簡體   English   中英

等待2個異步方法中的任何一個返回結果

[英]Waiting for any of 2 async methods to return a result

我想知道在處理異步編程時是否遵循最佳實踐。 我手頭的問題是:我同時與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;
    }            
}

基本上,這很好。

但是,一個問題是您總是放棄一項任務。 它被要求取消自身,但是如果不這樣做,它將繼續運行。 這會積累資源使用情況,並可能導致您看到的緩慢。

在.NET IO中,很少會輕易取消。 例如,對於套接字,您只能關閉套接字才能取消IO。 與USB設備通話時,您需要確保取消確實有效。

一個不同的問題是, cts可能在其令牌的最后使用之后被處置。 在您調用cts.Dispose() ,仍有一個正在運行的任務可能在令牌中注冊了自己。 我不確定是否可以保證正常工作。

您可以通過等待已取消的操作實際取消來解決此問題:

    cts.Cancel();
    await Task.WhenAll(tasks); //Maybe need to swallow exceptions.
    return response.Result;

您可以將Task.Run(async簡化為普通的方法調用。這會稍微改變語義。它會傳遞同步上下文,並同步執行async方法的某些部分。您可能想要或不希望這樣做。不是我的主觀評估是,此Task.Run模式澄清了代碼,並使代碼更容易正確使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM