包装一个使用基于事件的异步模式的库,用于 Async/Await
Wrapping a library that uses the Event-Based Asyncronous Pattern, for use with Async/Await
我在整个代码中使用 async/await 模式。但是,有一个 API 使用了基于事件的异步模式。我已经阅读了 MSDN 和几个 Whosebug 答案,这样做的方法是使用 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;
该方法总是 return 非常快,但没有触发任何事件。
如果我加上tcs.Task.Await();就在 return 语句之前,它可以工作,但这并没有提供我想要的异步行为。
我已经与我在 Internet 上看到的各种样本进行了比较,但没有发现任何差异。
我想我应该回答这个问题。
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;
}
问题在于,在您的 Process
方法终止后,您的 ServiceClient
局部变量有资格进行垃圾收集,并且可能在事件触发之前被收集,因此竞争条件是到位。
为了避免这种情况,我将 ProcessAsync
定义为类型上的 extension method:
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
对象存在某种 design/implementation 问题。我建议,如果可能的话,查看实现并查看是否有任何可能导致引用无法保持根目录。
我在整个代码中使用 async/await 模式。但是,有一个 API 使用了基于事件的异步模式。我已经阅读了 MSDN 和几个 Whosebug 答案,这样做的方法是使用 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;
该方法总是 return 非常快,但没有触发任何事件。
如果我加上tcs.Task.Await();就在 return 语句之前,它可以工作,但这并没有提供我想要的异步行为。
我已经与我在 Internet 上看到的各种样本进行了比较,但没有发现任何差异。
我想我应该回答这个问题。
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;
}
问题在于,在您的 Process
方法终止后,您的 ServiceClient
局部变量有资格进行垃圾收集,并且可能在事件触发之前被收集,因此竞争条件是到位。
为了避免这种情况,我将 ProcessAsync
定义为类型上的 extension method:
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
对象存在某种 design/implementation 问题。我建议,如果可能的话,查看实现并查看是否有任何可能导致引用无法保持根目录。