包装使用基于事件的异步模式的库,以便与Async/Await一起使用

本文关键字:一起 Await Async 异步 于事件 事件 包装 模式 | 更新日期: 2023-09-27 18:25:56

我在整个代码中使用异步/等待模式。然而,有一个API使用了基于事件的异步模式。我在MSDN上读过,也读过一些StackOverflow的答案,说实现这一点的方法是使用TaskCompletionSource。

我的代码:

public static Task<string> Process(Stream data)
{
    var client = new ServiceClient();
    var tcs = new TaskCompletionSource<string>();
    client.OnResult += (sender, e) =>
    {
        tcs.SetResult(e.Result);
    };
    client.OnError += (sender, e) =>
    {
        tcs.SetException(new Exception(e.ErrorMessage));
    };
    client.Send(data);
    return tcs.Task;
}

并称为:

string result = await Process(data);

或者,用于测试:

string result = Process(data).Result;

该方法总是很快返回,但这两个事件都没有被触发。

如果我加上tcs。Task.Await();就在return语句之前,它是有效的,但这并不能提供我想要的异步行为。

我把我在网上看到的各种样品进行了比较,但没有发现任何区别。

包装使用基于事件的异步模式的库,以便与Async/Await一起使用

问题在于,在Process方法终止后,ServiceClient局部变量有资格进行垃圾收集,并且可能在事件触发之前进行收集,因此存在竞争条件。

为了避免这种情况,我将ProcessAsync定义为类型的扩展方法

public static class ServiceClientExtensions
{
    public static Task<string> ProcessAsync(this ServiceClient client, Stream data)
    {
        var tcs = new TaskCompletionSource<string>();
        EventHandler resultHandler = null;
        resultHandler = (sender, e) => 
        {
            client.OnResult -= resultHandler;
            tcs.SetResult(e.Result);
        }
        EventHandler errorHandler = null;
        errorHandler = (sender, e) =>
        {
            client.OnError -= errorHandler;
            tcs.SetException(new Exception(e.ErrorMessage));
        };
        client.OnResult += resultHandler;
        client.OnError += errorHandler;
        client.Send(data);
        return tcs.Task;
    }
}

然后这样消费:

public async Task ProcessAsync()
{
    var client = new ServiceClient();
    string result = await client.ProcessAsync(stream);
}

编辑:@usr指出,通常情况下,IO操作应该是保持调用它们的引用有效的操作,而我们在这里看到的情况并非如此。我同意他的观点,这种行为有点奇怪,可能是ServiceClient对象的某种设计/实现问题的信号。如果可能的话,我建议查看实现,看看是否有任何原因可能导致引用无法扎根。

我想我应该回答这个问题。

public static Task<string> Process(Stream data)
{
    var handle = new AutoResetEvent(false);
    var client = new ServiceClient();
    var tcs = new TaskCompletionSource<string>();
    client.OnResult += (sender, e) =>
    {
        tcs.SetResult(e.Result);
        handle.Set();
    };
    client.OnError += (sender, e) =>
    {
        tcs.SetException(new Exception(e.ErrorMessage));
        handle.Set();
    };
    client.Send(data);
    handle.WaitOne(10000); // wait 10 secondds for results
    return tcs.Task;
}