【问题标题】:Waiting for events asynchronously in C#在 C# 中异步等待事件
【发布时间】:2018-12-27 09:21:36
【问题描述】:

我正在开发 WCF 服务/客户端,试图找出如何用不会阻塞调用者线程的东西替换 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();
    }
}

【问题讨论】:

  • “我知道代码有点丑,完全违背了使用异步编程的目的,我的道歉。” - 实际上,我认为它根本没有(好吧,除了阻止WaitOne,但这大概就是你要在这里解决的问题)

标签: c# wcf asynchronous taskcompletionsource


【解决方案1】:

可能最简单的方式来做你想做的事就是用你提到的TaskCompletionSource替换ManualResetEvent。例如:

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();

【讨论】:

  • 谢谢,我试试看效果如何。
  • 用这个做超时,效果很好int timeout = 20000;new Timer(t =&gt; tcs.SetResult(null), null, timeout, Timeout.Infinite);
【解决方案2】:

看起来您正在使用一些实现Event-based Asynchronous Pattern 的类。如果您正在使用 async,您真正想要的是使用实现 Task-based Asynchronous Pattern 的 API。

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

封装基于事件的异步模式 (EAP) 实现比封装 APM 模式更复杂,因为 EAP 模式比 APM 模式具有更多变化和更少结构。为了演示,下面的代码包装了DownloadStringAsync 方法。 DownloadStringAsync 接受一个 URI,在下载时引发 DownloadProgressChanged 事件以报告有关进度的多个统计信息,并在完成时引发 DownloadStringCompleted 事件。最终结果是一个字符串,其中包含指定 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。

【讨论】:

  • 感谢回复,我试试看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-04
  • 2019-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多