2013-01-22 18 views
2

所有的,我有一個情況,我被要求多線程一個大的'成本搗毀'算法。我是比較有經驗與Task S和將在採用類似Aysnc/Await VS.任務/繼續UI控制訪問

CancellationTokenSource cancelSource = new CancellationTokenSource(); 
CancellationToken token = cancelSource.Token; 
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task<bool> asyncTask = null; 
asyncTask = Task.Factory.StartNew<bool>(() => 
    SomeMethodAsync(uiScheduler, token, _dynamic), token); 

asyncTask.ContinueWith(task => 
{ 
    // For call back, exception handling etc. 
}, uiScheduler); 

和模式有信心然後,我需要提供和用戶界面操作的任何操作,我會用

Task task = Task.Factory.StartNew(() => 
{ 
    mainForm.progressLeftLabelText = _strProgressLabel; 
}, CancellationToken.None, 
    TaskCreationOptions.None, 
    uiScheduler); 

情況可能被包裹在一個方法中。

現在,我意識到我可以使這一切都變得更加複雜,並利用.NET 4.5的關鍵字async/await。不過,我有一些問題:如果我有一個地方CostEngine.ProgressInfo是被定義爲

public async Task ProcessScriptAsync(SSGForm doc, IProgress<ProgressInfo> progressInfo, 
            CancellationToken token, bool bShowCompleted = true) 
{ 
    ... 
    if (!await Task<bool>.Run(() => TheLongRunningProcess(doc))) 
     return 
    ... 
} 

用於返回進度信息和方法 ProcessScriptAsync一些基礎類的長時間運行的方法,我推出使用

// Start processing asynchroniously. 
IProgress<CostEngine.ProgressInfo> progressIndicator = 
    new Progress<CostEngine.ProgressInfo>(); 
cancelSource = new CancellationTokenSource(); 
CancellationToken token = cancelSource.Token; 
CostEngine.ScriptProcessor script = new CostEngine.ScriptProcessor(this); 
await script.ProcessScriptAsync(doc, progressIndicator, token); 

我有兩個問題:

  1. 要得到ProcessScriptAsync立即返回控制到UI 幾乎我立即等待一個新的Task<bool>代表(這似乎避免了連續的async/await s)。 這是否是撥打ProcessScriptAsync的正確方法? [「懶惰初始化」,通過在外包裹方法?]

  2. 要從TheLongRunningProcess內訪問UI,我僅僅通過在UI TaskScheduleruiScheduler;即TheLongRunningProcess(doc, uiScheduler),然後使用:


Task task = Task.Factory.StartNew(() => 
{ 
    mainForm.progressLeftLabelText = _strProgressLabel; 
}, CancellationToken.None, 
    TaskCreationOptions.None, 
    uiScheduler); 

如前?

對不起,感謝您的時間。

+0

既然你有兩個問題,他們真的應該問兩個不同的問題,而不是一個。 – Servy

+0

但這意味着複製大部分問題。我總是認爲/擔心這一點,但當問題如此嚴重相關時,複製超出界限的東西似乎有點矯枉過正...... – MoonKnight

+0

呃。這其中大部分並不適用於第二個問題,也不需要在那裏。它也允許你得到一個問題的答案,而不是另一個問題,如果有人知道只有一個答案。如果你問這兩個問題,你只會將答案限制在知道兩者答案的人身上。 – Servy

回答

2
  1. 這取決於。你已經展示了很多代碼,但卻忽略了你實際上提出問題的那一點。首先,不知道代碼是什麼,我們無法知道它是否真的需要一段時間。接下來,如果你等待已經完成的任務,它會意識到這一點,而不是安排延續,而是繼續(這是一個優化,因爲調度任務很耗時)。如果您未完成的任務未完成,則繼續將在調用SynchronizationContext中執行,這將再次保持UI線程繁忙。您可以使用ConfigureAwait(false)來確保繼續運行在線程池中。這應該處理這兩個問題。請注意,通過這樣做,您不能再訪問ProcessScriptAsync...部分中的UI控件(無需執行任何特殊操作)。另請注意,因爲ProcessScriptAsync現在正在線程池線程中執行,所以不需要使用Task.Run將方法調用移動到後臺線程。

  2. 這是一種選擇,是的。雖然,如果您正在根據進度更新UI,那麼IProgress適用於此。我看到你已經在使用它了,所以這是做這件事的最佳模式。如果這是更新與您現有的IProgress相比的單獨進度類型(即狀態文本,而不是作爲int完成的百分比),則可以傳遞一秒。

+0

非常感謝您的時間。我已經省略了'...'的代碼,'TheLongRunningProcess(doc)'中的代碼是VAST。 TheLongRunningProcess(doc)'本身會調用多種其他方法,並且我想用最少量的大量/大量工作來多線程化。對於2.,UI訪問將圍繞Excel工作簿移動單元格(由SpreadsheetGear控制)。所以這與進展無關,但要指出腳本的哪一部分正在執行。 – MoonKnight

+1

@Killercam'「所以這與進程沒有關係,而是指示腳本當前正在執行的部分。」腳本執行的部分*表示進度,這是答案的全部。 – Servy

1

我想嘗試來回切換後臺線程(用於CPU密集型操作或IO操作沒有async支持)和UI線程之間(操縱UI控件)通常是設計不良的徵兆。你的計算和你的UI代碼應該是分開的。

如果您只是爲了通知用戶界面某種進度,然後使用IProgress<T>。線程之間的任何編組都將成爲實現該接口的責任,您可以使用Progress<T>,它使用SynchronizationContext正確執行。

如果你無法避免混合後臺線程代碼和UI線程代碼,你的UI工作不是進度報告(所以IProgress<T>不適合),我可能會將每一位後臺線程代碼放入它自己的await Task.Run() ,並將UI代碼留在最上層。

您使用單個Task.Run()運行後臺線程代碼,然後切換到UI線程使用StartNew()uiScheduler的解決方案也可以工作。在這種情況下,一些輔助方法可能會很有用,特別是如果您想在UI代碼中使用await。 (否則,你就必須記住翻一番awaitStartNew()結果)

另一種選擇是創建一個SwitchTo(TaskScheduler)方法,awaiter一直持續給定的調度器會返回一個自定義的。這種方法在一些異步CTP中,但因爲它是deemed too dangerous when it comes to handling exceptions而被刪除。

+0

非常感謝您的時間。 – MoonKnight

+0

只是一個快速的;我知道我應該使用IProgress對象來更新用戶界面,而這樣做是不好的設計。然而,在腳本處理器中完成的一件事是通過工作表獲取命令,因此我不得不訪問UI控件 - 所以我需要在某個時刻更改TaskScheduler。 – MoonKnight