我想知道在处理异步编程时是否遵循最佳实践。我手边的问题是:我正在同时与2台设备通话。我可以使用SendMessageAsync(msg)
方法向他们发送消息。两个设备同时收到此消息,但作为回报,只有一个设备发送回复,而另一个设备根本没有回复。
此方法也应该接受CancellationToken
,例如在超时或其他原因后,整个事情都可以取消。
所以我写了这个方法来阅读消息:
public async Task<Message> GetMessageAsync(CancellationToken token)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, new CancellationToken(false)))
{
var device1 = Task.Run(async () => { return await GetDevice1Async(cts.Token); });
var device2 = Task.Run(async () => { return await GetDevice2Async(cts.Token); });
var response = await Task.WhenAny(device1, device2);
cts.Cancel(); //Only one device answers, so cancel the other other one
return response.Result;
}
}
我想知道我的解决方案是否遵循最佳做法。具体来说,我有兴趣获得非常好的性能(我与之交谈的设备是USB设备,所以我希望能够快速为它们提供服务)。因此,每次需要阅读消息时,我都不满意创建两个任务。
目前我的解决方案似乎有效,但据报道,在某些机器上,它运行缓慢。然而,在我的机器上,它运行得非常快,所以我不知道是不是因为我的代码或其他问题。
我做得对吗?有没有办法改进这个解决方案?
编辑: 根据Jeroen Mostert和usr的建议,我更新了以下代码:
public async Task<Message> GetMessageAsync(CancellationToken token)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, new CancellationToken(false)))
{
var taskList = new List<Task<Message>> {
GetDevice1Async(cts.Token),
GetDevice2Async(cts.Token)
};
// wait for any operation to finish, then cancel the other one
var task = await Task.WhenAny(taskList);
cts.Cancel();
//ensure both operations are either finished or cancelled before returning
try {
await Task.WhenAll(taskList);
}
catch (OperationCanceledException)
{
//The exception is expected as is safe to ignore
}
return task.Result;
}
}
答案 0 :(得分:2)
基本上,这很好。
然而,一个问题是你总是放弃一项任务。要求取消自己,但如果不这样做,它将继续运行。这可能会累积资源使用量,并可能导致您看到的速度缓慢。
在.NET中,很少可以轻松取消IO。例如,对于套接字,除了关闭套接字之外,您无法取消IO。与USB设备通信时,您需要确保取消确实有效。
另一个问题是cts
可能在最后一次使用其令牌后被处理掉。当您致电cts.Dispose()
时,仍有一项任务正在运行,可能使用令牌注册自己。我不确定这是否有效。
您可以通过等待取消的操作实际取消来解决此问题:
cts.Cancel();
await Task.WhenAll(tasks); //Maybe need to swallow exceptions.
return response.Result;
您可以将Task.Run(async
简化为正常的方法调用。这稍微改变了语义。它传递同步上下文,并同步执行异步方法的某些部分。您可能想要也可能不想要。这不是一个重要的效率问题。我的主观评估是这个Task.Run
模式澄清了代码并使代码更容易正确。