簡體   English   中英

運行多個異步任務-更快的結果鏈接與並行?

[英]Running Multiple Async Tasks - Faster results Chaining vs Parallel?

我想同時進行7個不同的API調用,並且希望每個完成后都可以更新UI。

我一直在擺弄兩種不同的方法,將請求鏈接起來,並同時(並行)關閉所有請求。

兩者似乎都可以工作,但是由於某種原因,我的並行任務比鏈接它們花費的時間長得多。

我是TPL /並行技術的新手,所以可能是我的代碼不正確,但是由於每個請求都必須在下一次啟動之前完成,因此鏈接請求是否需要花費更長的時間? 而不是並行運行,它們都立即消失了,所以您只需要等待最慢的時間?

如果您看到我的邏輯或代碼中的錯誤,請告訴我。 我對收到的回復時間感到滿意,但我不明白為什么。

我的“鏈接”代碼:

        await (Task.Run(() => WindLookup_DoWork()).
            ContinueWith((t) => WindLookup_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => OFAC_DoWork()).ContinueWith((t) => OFAC_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => BCEGS_DoWork()).ContinueWith((t) => BCEGS_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => BOPTerritory_DoWork()).ContinueWith((t) => BOPTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => TerrorismTerritory_DoWork()).ContinueWith((t) => TerrorismTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => ProtectionClass_DoWork()).ContinueWith((t) => ProtectionClass_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()).
            ContinueWith((t) => AddressValidation_DoWork()).ContinueWith((t) => AddressValidation_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));

我的“並行”代碼:

        List<Task> taskList = new List<Task>();
        taskList.Add(Task.Run(() => WindLookup_DoWork()).
            ContinueWith((t) => WindLookup_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => BCEGS_DoWork()).
            ContinueWith((t) => BCEGS_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => BOPTerritory_DoWork()).
            ContinueWith((t) => BOPTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => TerrorismTerritory_DoWork()).
            ContinueWith((t) => TerrorismTerritory_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => ProtectionClass_DoWork()).
            ContinueWith((t) => ProtectionClass_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => OFAC_DoWork()).
            ContinueWith((t) => OFAC_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));
        taskList.Add(Task.Run(() => AddressValidation_DoWork()).
            ContinueWith((t) => AddressValidation_ProcessResults(), TaskScheduler.FromCurrentSynchronizationContext()));

        await Task.WhenAll(taskList.ToArray());

我基本上已經轉換了舊的背景工作程序代碼,這就是為什么有DoWork方法和更新UI的“回調”方法的原因。

DoWork方法調用API的POST方法,並且處理結果僅用響應xml填充文本區域。

我一直在擺弄兩種不同的方法,將請求鏈接起來,並同時(並行)關閉所有請求。

區分並發並行很重要。 對於您的情況,您只想同時完成所有操作,這是一種並發形式。 並行是一種更具體的技術,它使用多個線程來實現並發,這適用於CPU密集型工作。 但是,在您的情況下,工作是受I / O約束的(API請求),在這種情況下,更合適的並發形式將是異步的。 異步使用線程的並發形式。

兩者似乎都可以工作,但是由於某種原因,我的並行任務比鏈接它們花費的時間長得多。

我不確定為什么它們會顯着更長,但是一個常見的問題是,同時請求的數量受到限制,無論是在客戶端( ServicePointManager.DefaultConnectionLimit )還是在服務器端(例如,參見並發請求)和會話狀態 )。

如果您看到我的邏輯或代碼中的錯誤,請告訴我。

不幸的是,TPL很難從參考文檔或IntelliSense中學習,因為有太多的方法和類型只應在非常特殊的情況下使用。 特別是, 不要在您的方案中使用ContinueWith 它具有StartNew所做相同問題 (兩個鏈接均指向我的博客)。

更好(更可靠,更易於維護)的方法是引入一些輔助方法:

async Task WindLookupAsync()
{
  await Task.Run(() => WindLookup_DoWork());
  WindLookup_ProcessResults();
}
// etc. for the others

// Calling code (concurrent):
await Task.WhenAll(
    WindLookupAsync(),
    BCEGSAsync(),
    BOPTerritoryAsync(),
    TerrorismTerritoryAsync(),
    ProtectionClassAsync(),
    OFACAsync(),
    AddressValidationAsync()
);

// Calling code (serial):
await WindLookupAsync();
await BCEGSAsync();
await BOPTerritoryAsync();
await TerrorismTerritoryAsync();
await ProtectionClassAsync();
await OFACAsync();
await AddressValidationAsync();

使用重構的代碼,就不需要ContinueWith或顯式的TaskScheduler

但是,每個請求仍在為每個請求刻錄線程池線程。 如果這是台式機應用程序,那不是世界末日,但它沒有使用最好的解決方案。 正如我在回答開頭提到的那樣,更適合解決此問題的方法是異步而不是並行

要使代碼異步,您應該首先從POST API調用開始,然后將其更改為使用異步版本,然后使用await調用。 (附帶說明: WebClient確實具有異步方法,但是請考慮將HttpClient更改為更自然地適合async )。 一旦使用await調用該POST API,這將要求_DoWork方法變得異步(並返回Task而不是void )。 在這一點上,您可以更改上面的幫助程序方法以直接await這些方法,而不是使用Task.Run ,例如:

async Task WindLookupAsync()
{
  await WindLookup_DoWork();
  WindLookup_ProcessResults();
}

調用代碼(並發和串行版本)保持不變。

暫無
暫無

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

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