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