2012-11-01 64 views
5

我有一個Task,我開始並希望等待在WPF應用程序中完成。在這個任務中,我調用調度器上的ActionTask.Wait vs Task.RunSyncronously任務調用WPF Dispatcher.Invoke

如果我使用Task.Wait()它似乎掛起,就好像該方法從未完成。此外,Dispatcher.Invoke內的斷點永遠不會被命中。

如果我使用Task.RunSyncronously()它看起來工作正常,分派器內的斷點被擊中。

爲什麼會有差異?

代碼示例如下:

public void ExampleMethod() 
{ 
    // When doing the following: 
    var task = new Task(LoadStuff); 

    // This never returns: 
    task.Start(); 
    task.Wait(); 

    // This version, however, does: 
    task.RunSyncronously(); 
} 

private void LoadStuff() 
{ 
    ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true); 

    DispatchHelper.RunOnDispatcher(() => 
    { 
     ... 
    }); 
} 

public static class DispatchHelper 
{ 
    public static void RunOnDispatcher(Action action) 
    { 
     Application.Current.Dispatcher.Invoke(action); 
    } 
}  

回答

5

是的,有一個重大的區別。如果您使用RunSyncronously,則只需在UI線程中運行該任務。如果您在後臺線程中啓動它並且我們Wait則代碼在後臺線程中運行並且UI線程被阻止。如果該任務中的代碼正在調用UI線程,並且UI線程被阻塞(由Wait),那麼您創建了一個死鎖,並且應用程序將保持凍結狀態。

請注意,如果您使用了從非UI線程執行該任務的RunSyncronously,並且UI線程被其他東西阻塞,您仍然會看到死鎖。現在

,爲你應該做的,真的有兩個選擇:

  1. 任務本身實際上並不需要很長的時間,它真的應該在UI線程中運行,而不是在後臺線程中。 UI線程不會被凍結(暫時)足夠長的時間,直接成爲在UI中執行所有這些工作的問題。如果是這樣的話,你甚至可能不應該把它變成一個任務,只要把代碼放在一個方法中並調用方法即可。

  2. 該任務確實需要很長時間才能運行,然後在完成該工作後更新UI。如果是這樣的話,重要的是它不是RunSyncronously,而是在後臺線程中啓動。爲了防止你的整個應用程序發生死鎖,這意味着你需要而不是通過調用Wait來阻止UI線程。如果您在任務完成後想要運行某些代碼,則需要執行的操作是爲該任務添加一個延續。在C#4.0中,可以通過在任務上調用ContinueWith並添加要運行的委託來完成。在C#5.0 +中,您可以改爲在相關任務上執行await(而不是Wait,這實際上是一個很大的區別),它會自動連接剩餘的方法以作爲您的延續運行(實際上它是語法糖爲明確ContinueWith調用,但它是一個非常有用的)。

+0

難道你不是指.NET 4.5,還沒有.NET 5。此外,異步方法生成狀態機而不是使用ContinueWith。 –

+0

@BrianReichle我的意思是C#,而不是.NET,但是,這是錯誤的。就「異步」而言,它確實生成了一個狀態機,並且該狀態機將自身添加爲正在等待的任務的延續。狀態機是一種機制,它可以在繼續運行時知道如何選擇停止的位置,但是對正在等待的任務添加延續是它在「Task」完成時如何運行*任何事情*。 – Servy

+0

狀態機通過'GetAwaiter()'而不是'ContinueWith'返回的awaiter重新計劃(這允許我們等待除了任務之外的其他事情)。它們都調用通用的實現,但是task.GetAwaiter()。OnCompleted(...)不調用ContinueWith(特別是前者避免了創建新任務)。但我想這只是迂腐,大多數人不會關心實現細節:) –