当我需要等待事件时如何使方法异步

Posted

技术标签:

【中文标题】当我需要等待事件时如何使方法异步【英文标题】:How to make a method asynchronous when I need to wait for an event 【发布时间】:2017-07-27 10:39:20 【问题描述】:

我做了一个这样的函数:

public void GetData(string dataToPost)

    var url = "some URL";

    using (var client = new WebClient())
    
        client.Headers[HttpRequestHeader.ContentType] = "application/json";

        client.UploadStringCompleted += (s, e) =>
            
                Console.WriteLine("Result is here");
                Console.WriteLine(e.Result);
            ;

        client.UploadStringAsync(new System.Uri(url), "POST", dataToPost);
    

这里的问题是我想要从服务器返回一个值(HTTP 响应)。

我希望我的函数是异步的,因此例如我希望能够在一个循环中调用它 5 次,而无需等待每次调用在下一次调用之前返回某些内容。

我不知道如何实现它。我试图创建一些 Task 对象来等待它,但是 Task 需要一个委托,我不知道我可以给它什么委托。

上面的 Te 函数使用了一个事件,该事件在结果到来时被触发 - 所以我需要返回该结果。

我该怎么做?

【问题讨论】:

这是一个控制台应用程序。我对这一切都很陌生,所以我不确定我是否理解你的意思。 UploadStringAsync 返回无效。 //edit 好像有人删了他的评论 对不起,我以为你在使用HttpClient。您需要订阅UploadStringCompleted 事件,该事件将在调用完成后被调用。 是否可以切换到HttpClient,因为它具有原生异步/等待支持,例如msdn.microsoft.com/en-us/library/hh138242.aspx。 @Igor,我没关系,如果你知道用HttpClient做的方法,我可以用它 在这种情况下,请参阅上一个问题/答案:***.com/a/15176685/1260204 【参考方案1】:

WebClient 有一个返回 Task<string> 的重载:

https://msdn.microsoft.com/en-us/library/hh193920(v=vs.110).aspx

您可以触发所有请求,然后使用Task.WhenAll 等待它们:

public void GetData(string dataToPost)

    var url = "some URL";
    var url2 = "some other URL";

    using (var client = new WebClient())
    
        client.Headers[HttpRequestHeader.ContentType] = "application/json";

        var task1 = client.UploadStringTaskAsync(new System.Uri(url), "POST", dataToPost);
        var task2 = client.UploadStringTaskAsync(new System.Uri(url2), "POST", dataToPost);

        var results = Task.WhenAll(task1, task2).Result;

        foreach (var result in results)
        
            Console.WriteLine("Result is here");
            Console.WriteLine(result);
        
    

如果您可以将 GetData 更改为 async 方法,那就更好了,您可以在 Task.WhenAll 上使用 await

【讨论】:

【参考方案2】:

正如在 cmets 中已经说过的,HttpClient 是此类任务更自然的选择。但是,您仍然可能想知道如何提供订阅事件的任务,这可以通过 TaskCompletionSource<T> 完成,只需对您的代码进行非常小的更改:

private Task<string> GetData(string dataToPost)

    var url = "some URL";
    var resultSource = new TaskCompletionSource<string>();

    using (var client = new WebClient())
    
        client.Headers[HttpRequestHeader.ContentType] = "application/json";

        client.UploadStringCompleted += (s, e) => 
            Console.WriteLine("Result is here");
            Console.WriteLine(e.Result);

            // this will complete the task
            resultSource.SetResult(e.Result);
        ;

        client.UploadStringAsync(new System.Uri(url), "POST", dataToPost);
    

    return resultSource.Task;

在这种情况下,您还可以设置cancellation(也可以使用given token)和exception(即使使用multiple exceptions),这样它自然会满足您的需求。对于并发事件订阅的情况,这三种方法都可以Try*方式完成。

还要注意@Stefanod'Antonio' 对async method 过载的回答。

【讨论】:

感谢您的意见,这很有趣。在我看来,这就像 javascript 的承诺。 C# 中的 Task 和 Javascript 中的 Promise 几乎是等价的。

以上是关于当我需要等待事件时如何使方法异步的主要内容,如果未能解决你的问题,请参考以下文章

如何使用回调在任何函数中调用异步等待方法

如何使节点 fs 与类异步/等待?

在 Windows 服务的 timer_elapsed 事件处理程序中使用异步等待

线程和异步的分别与联系

将 Node.js 流错误事件传播到异步等待样式代码

如何等待异步方法完成?