在 C# 中异步等待事件

Waiting for events asynchronously in C#

我正在开发 WCF service/client 试图找出如何用不会阻塞调用者线程的东西替换 ManualResetEvent。
最重要的是 await client.CloseAsync()FinishedEventReceived 事件被触发之前不会被调用。

我考虑过使用 TaskCompletionSource,但我有点不确定在这种情况下它会如何工作。

我知道代码有点难看,完全违背了使用异步编程的目的,抱歉。
有什么想法吗?

private async Task CallServiceMethodAndWaitForEvent()
{
    var mre = new ManualResetEvent(true);

    var client = new AwesomeClient();
    client.FinishedEventReceived += (s, e) =>
    {
        // Do something with the result, this event is only fired once.
        mre.Set();
    };
    client.UpdateEventReceived += (s, e) =>
    {
        // This even can fire several times before the finished event.
    };

    try
    {
        var parameters = new Parameters()
        {
            SomeParameter = "Test123",
            TestAmount = 10000,
        };

        var errors = await client.DoWorkAsync(parameters);
        Debug.WriteLine(errors);

        mre.WaitOne(TimeSpan.FromSeconds(20));
        await client.CloseAsync();
    }
    catch (FaultException ex)
    {
    }
    catch (Exception)
    {
        client.Abort();
    }
}

看起来您正在使用一些实现 Event-based Asynchronous Pattern. What you really want, if you're doing async, is to work with APIs that implement the Task-based Asynchronous Pattern.

的 class

值得庆幸的是,Microsoft 提供了有关 adapting EAP to look like TAP 的具体指导:

Wrapping an Event-based Asynchronous Pattern (EAP) implementation is more involved than wrapping an APM pattern, because the EAP pattern has more variation and less structure than the APM pattern. To demonstrate, the following code wraps the DownloadStringAsync method. DownloadStringAsync accepts a URI, raises the DownloadProgressChanged event while downloading in order to report multiple statistics on progress, and raises the DownloadStringCompleted event when it's done. The final result is a string that contains the contents of the page at the specified URI.

public static Task<string> DownloadStringAsync(Uri url)
 {
     var tcs = new TaskCompletionSource<string>();
     var wc = new WebClient();
     wc.DownloadStringCompleted += (s,e) =>
         {
             if (e.Error != null) 
                tcs.TrySetException(e.Error);
             else if (e.Cancelled) 
                tcs.TrySetCanceled();
             else 
                tcs.TrySetResult(e.Result);
         };
     wc.DownloadStringAsync(url);
     return tcs.Task;
}

希望您可以适应您正在使用的特定 API。

可能 最简单的 方法是将 ManualResetEvent 替换为 - 正如您提到的 - TaskCompletionSource。例如:

var tcs = new TaskCompletionSource<int>();

var client = new AwesomeClient();
client.FinishedEventReceived += (s, e) =>
{
    // Do something with the result, this event is only fired once.
    tcs.SetResult(42); // number here is a dummy, since you only want Task
};

...

await tcs.Task;
await client.CloseAsync();

请注意,超时方面更难;一种常见的方法是使用 Task.Delay 作为后备,以及 Task.WhenAny,即

var timeout = Task.Delay(timeoutInterval);
if (timeout == await Task.WhenAny(timeout, tcs.Task))
    throw new TimeoutException();