2017-03-22 83 views
1

我有幾個需要同步回主UI線程的異步方法。如何在異步方法運行時阻止方法而不會導致UI線程中的死鎖

async Task MyAsyncMethod() { 
    await DoSomeThingAsync().ConfigureAwait(true); 
    DoSomethingInTheGui(); 
} 

現在我需要從從GUI線程引發了syncronous事件處理程序調用它們,以及事件處理程序無法完成,直到異步方法完成。所以MyAsyncMethod().Wait()不是一種選擇,也不是某種「即丟即用」解決方案。

使用Nito.AsyncEx似乎有前途的這種做法,但它仍然死鎖:https://stackoverflow.com/a/9343733/249456

我發現的唯一的解決辦法似乎是一個黑客對我說:

public void RunInGui(Func<Task> action) 
{ 
    var window = new Window(); 
    window.Loaded += (sender, args) => action() 
     .ContinueWith(p => { 
      window.Dispatcher.Invoke(() => 
      { 
       window.Close(); 
      }); 
     }); 
     window.ShowDialog(); 
} 

有沒有辦法讓同樣的效果(阻止調用方法,允許同步返回到gui線程)而不創建新窗口?

注意:我知道重構可能是最好的選擇,但這是一項我們必須在更長時間內做的大量工作。另外值得一提的是,這是Autodesk Inventor的一個插件。 api有幾個怪癖,並且所有的API調用(甚至與非ui相關的)都必須從主/ UI線程執行。

另外值得一提的是,我們保留對主線程調度程序的引用,並在代碼庫中使用MainThreadDispatcher.InvokeAsync(() => ...)

+4

你不需要屏蔽你的UI線程。只要你有阻止你的UI線程的要求(特別是當其他進程實際上需要使用UI線程時),那麼你將會處於一個受到傷害的世界。如果您重視您的理智,請刪除該要求。 – Servy

+0

*爲什麼*使用同步事件處理程序?爲什麼不是一個*異步和事件處理程序?例如'async void Button_Click(..){await MyAsyncMethod1();等待MyAsyncMethod2();}' –

+0

另一種選擇是使用進度將消息發送回UI線程。 Progress 的負載不一定是字符串或進度消息,它可以是任何對象。檢查[4.5中的異步:在異步API中啓用進度和取消](https://blogs.msdn.microsoft.com/dotnet/2012/06/06/async-in-4-5-enabling-progress-and-cancellation -in-async-apis /) –

回答

7

我發現的唯一的解決辦法似乎是一個黑客對我

所有同步過異步解決方案是黑客。我列舉了我最常見的article on brownfield async。值得注意的是,對於任意代碼沒有解決方案。 每個解決方案在某些情況下會出現死鎖或拋出。

一位黑客更容易的是在一個線程池線程塊:

Task.Run(() => action()).Wait(); 

但是,如果你的action()需要一個UI背景,那麼這是行不通的。

下一步是嘗試使用單線程上下文。這是我的AsyncContext類型所採取的做法:

AsyncContext.Run(() => action()); 

如果action正在等待UI線程的消息隊列被抽這會死鎖。由於這種僵局,但Window的作品,這似乎是這種情況。

如果進一步下降,您可以使用nested pumping。請注意,nested pumping opens up a whole world of hurt due to unexpected reentrancy。 GitHub上有一些代碼顯示how to do nested pumping on WPF。但是,我建議您重構代碼而不是重新進行重入,因此我建議您使用高度的

+0

感謝您的快速回復。重構是我的首選解決方案,但這是一項巨大的任務,因此它必須逐步完成。我怎樣才能確定我的代碼在UI線程消息隊列中被抽取的時間? – Henrik

+0

@Henrik:你可以使用'AsyncContext',然後當它死鎖看看你的UI線程上的調用堆棧。 –

相關問題