gpt4 book ai didi

c# - 任务取消暂停 UI

转载 作者:行者123 更新时间:2023-11-30 22:03:48 24 4
gpt4 key购买 nike

接下来的情况让我有点困惑。如果我调用 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");
}
}

最佳答案

由于 Dispatcher.Invoke 调用,SleepBeforeInvokeSleepAfterInvoke 都有一个潜在的死锁 - 只是你有那么多更有可能在 SleepBeforeInvoke 中触发它,因为您在问题发生的地方人为地创建了 500 毫秒的延迟,而不是在其他情况下可以忽略不计(可能是纳秒)的窗口。

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

The app starts and the task is wired up.

The task runs on a thread pool thread, but periodically blocks on a synchronous call marshalled to your UI (dispatcher) synchronization context. The task has to wait for this call to complete before it can proceed to the next loop iteration.

When you press the button, cancellation will be requested. It will most likely happen while the task is executing Thread.Sleep. Your UI thread will then block waiting for the task to finish (_task.Wait), which will never occur, because right after your task finishes sleeping it won't check whether it's been cancelled and will try to make a synchronous dispatcher call (on the UI thread, which is already busy due to _task.Wait), and ultimately deadlock.

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

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

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

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

关于c# - 任务取消暂停 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25779748/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com