2014-09-11 122 views
2

我很滿意以下情況。如果我調用SleepBeforeInvoke方法,則應用程序暫掛在_task.Wait();字符串中。但如果我打電話SleepAfterInvoke方法,應用程序工作正常,控制將達到catch條款。調用BeginInvoke方法也可以。任務取消暫停UI

任何人都可以解釋最大的細節這三種方法的用法有什麼區別?如果我使用SleepBeforeInvoke方法,爲什麼應用程序被暫停,爲什麼我不使用SleepAfterInvokeBeginInvoke方法?謝謝。

的Win 7,.NET 4.0

XAML:

<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition></RowDefinition> 
     <RowDefinition></RowDefinition> 
    </Grid.RowDefinitions> 
    <TextBlock Grid.Row="0" 
       Name="_textBlock" 
       Text="MainWindow"></TextBlock> 
    <Button Grid.Row="1" 
      Click="ButtonBase_OnClick"></Button> 
</Grid> 

的.cs:

public partial class MainWindow : Window 
{ 
    private readonly CancellationTokenSource _cts = new CancellationTokenSource(); 
    private Task _task; 


    /// <summary> 
    /// Application wiil be suspended on string _task.Wait(); 
    /// </summary> 
    private void SleepBeforeInvoke() 
    { 
     for (Int32 count = 0; count < 50; count++) 
     { 
      if (_cts.Token.IsCancellationRequested) 
       _cts.Token.ThrowIfCancellationRequested(); 

      Thread.Sleep(500); 
      Application.Current.Dispatcher.Invoke(new Action(() => { })); 
     } 
    } 

    /// <summary> 
    /// Works fine, control will reach the catch 
    /// </summary> 
    private void SleepAfterInvoke() 
    { 
     for (Int32 count = 0; count < 50; count++) 
     { 
      if (_cts.Token.IsCancellationRequested) 
       _cts.Token.ThrowIfCancellationRequested(); 

      Application.Current.Dispatcher.Invoke(new Action(() => { })); 
      Thread.Sleep(500); 
     } 
    } 


    /// <summary> 
    /// Works fine, control will reach the catch 
    /// </summary> 
    private void BeginInvoke() 
    { 
     for (Int32 count = 0; count < 50; count++) 
     { 
      if (_cts.Token.IsCancellationRequested) 
       _cts.Token.ThrowIfCancellationRequested(); 

      Thread.Sleep(500); 
      Application.Current.Dispatcher.BeginInvoke(new Action(() => { })); 
     } 
    } 


    public MainWindow() 
    { 
     InitializeComponent(); 
     _task = Task.Factory.StartNew(SleepBeforeInvoke, _cts.Token, TaskCreationOptions.None, TaskScheduler.Default); 
    } 

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e) 
    { 
     try 
     { 
      _cts.Cancel(); 
      _task.Wait(); 
     } 
     catch (AggregateException) 
     { 

     } 
     Debug.WriteLine("Task has been cancelled"); 
    } 
} 
+0

任務異步應該取代您對線程的需求。除非你知道你在做什麼,否則堅持單線程異步。另請閱讀Stephen Cleary的異步博客。 – Aron 2014-09-11 06:51:30

+0

@Aron,.net 4.0 :(沒有異步等待 – monstr 2014-09-11 06:59:54

+0

我會建議在這種情況下使用Rx.Net或升級...嚴重...它的價值升級...如果只是因爲'任務'被打破。 net 4.0,不要使用它''Task.ContinueWith'#$%* s你的'SynchronizationContext.Current',並且基本上破壞了WinForms和WPF – Aron 2014-09-11 07:00:47

回答

3

兩個SleepBeforeInvokeSleepAfterInvoke在他們潛在的死鎖由於Dispatcher.Invoke通話 - 只是因爲你更有可能在SleepBeforeInvoke中擊中它,因爲喲你會在問題發生時創建一個500ms的延遲時間,而在另一個情況下可能會忽略不計(可能是納秒)窗口。

該問題是由於Dispatcher.InvokeTask.Wait的阻塞性質造成的。以下是SleepBeforeInvoke的流程大致如下所示:

應用程序啓動並且任務已連線。

該任務在線程池線程上運行,但週期性阻塞編組到您的UI(調度程序)同步上下文的同步調用。任務必須等待此呼叫才能完成,然後才能進入下一個循環迭代。

當您按下按鈕時,將要求取消。這將很可能發生在任務執行時Thread.Sleep。你的用戶界面線程將阻止等待任務完成(_task.Wait),永遠不會發生這種情況,因爲在你的任務完成休眠後它不會檢查它是否被取消,並且將嘗試進行同步調度程序調用(在UI上線程,由於_task.Wait而已經處於忙碌狀態),並最終導致死鎖。

你可以(有點)通過在睡眠之後有另一個_cts.Token.ThrowIfCancellationRequested();來解決這個問題。

SleepAfterInvoke例如,未觀察到該問題的原因是定時:你的CancellationToken始終檢查前右側的同步調度呼叫,因而可能性調用_cts.Cancel將支票和調度呼叫之間發生的可以忽略不計,因爲兩者非常接近。

您的BeginInvoke示例根本不會表現出上述行爲,因爲您正在刪除造成死鎖阻止呼叫的事情。Dispatcher.BeginInvoke是非阻塞的 - 它在將來某個時間調度調度器,並立即返回而不用等待調用完成,從而允許線程池任務移動到下一個循環迭代,並且打到ThrowIfCancellationRequested

只是爲了好玩:我建議你把類似的東西Debug.Print您傳遞到Dispatcher.BeginInvoke委託裏面,一個接一個_task.Wait。您會注意到,由於_task.Wait阻止了UI線程,這意味着在請求取消之後委託傳遞給Dispatcher.BeginInvoke的操作不會執行,直到您的按鈕處理程序完成運行,您將注意到它們不會按照您的預期順序執行。

+0

明白了, спасибо:-) – monstr 2014-09-11 07:39:47

+0

@monstr,很樂意幫忙。 * Pozhaluista。* – 2014-09-11 07:55:29