【问题标题】:Task cancellation suspends UI任务取消暂停 UI
【发布时间】:2014-11-04 22:45:23
【问题描述】:

我对接下来的情况有点困惑。如果我调用SleepBeforeInvoke 方法,应用程序将在_task.Wait(); 字符串上挂起。但如果我调用SleepAfterInvoke 方法,应用程序工作正常,控制将达到catch 子句。调用BeginInvoke 方法也可以正常工作。

谁能详细解释这三种方法的用法有什么区别?如果我使用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");
    }
}

【问题讨论】:

标签: c# wpf task suspend cancellation


【解决方案1】:

SleepBeforeInvokeSleepAfterInvoke 由于Dispatcher.Invoke 调用而在它们中存在潜在的死锁 - 只是你更有可能在SleepBeforeInvoke 中遇到它,因为你正在创建一个人为的 500 毫秒问题发生的延迟时间,而在另一种情况下则可以忽略不计(可能是纳秒)。

问题是由于Dispatcher.InvokeTask.Wait 的阻塞性质造成的。以下是SleepBeforeInvoke 的流程大致如下:

应用启动,任务完成。

任务在线程池线程上运行,但会定期阻塞编组到 UI(调度程序)同步上下文的同步调用。任务必须等待此调用完成才能继续进行下一个循环迭代。

当您按下按钮时,将要求取消。它很可能在任务执行Thread.Sleep 时发生。然后,您的 UI 线程将阻塞等待任务完成 (_task.Wait),这永远不会发生,因为在您的任务完成睡眠后,它不会检查它是否已被取消,并会尝试进行同步调度程序调用 (on UI 线程,由于_task.Wait) 已经很忙,最终死锁。

您可以(在某种程度上)通过在睡眠后使用另一个 _cts.Token.ThrowIfCancellationRequested(); 来解决此问题。

SleepAfterInvoke 示例中未观察到问题的原因在于时间安排:您的CancellationToken 总是在同步调度程序调用之前检查,因此对_cts.Cancel 的调用很可能将发生在检查和调度程序之间的调用可以忽略不计,因为两者非常接近。

您的BeginInvoke 示例根本没有表现出上述行为,因为您正在删除导致死锁的东西 - 阻塞调用。 Dispatcher.BeginInvoke 是非阻塞的 - 它只是在未来某个时间“安排”调度程序上的调用并立即返回而不等待调用完成,从而允许线程池任务继续进行下一个循环迭代,并命中ThrowIfCancellationRequested.

只是为了好玩:我建议您在传递给Dispatcher.BeginInvoke 的委托中添加类似Debug.Print 的内容,并在_task.Wait 之后添加另一个内容。您会注意到它们没有按您期望的顺序执行,因为_task.Wait 阻塞了 UI 线程,这意味着在请求取消后传递给Dispatcher.BeginInvoke 的委托在您的按钮之前不会执行处理程序完成运行。

【讨论】:

  • @monstr,很乐意为您提供帮助。 波扎鲁斯塔。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 2021-07-13
  • 2018-07-04
  • 2016-03-26
  • 1970-01-01
相关资源
最近更新 更多