在用户界面和控制台应用程序中使用 Task.Yield() 的区别
Posted
技术标签:
【中文标题】在用户界面和控制台应用程序中使用 Task.Yield() 的区别【英文标题】:Difference between using Task.Yield() in a User Interface and Console App 【发布时间】:2021-04-21 08:59:03 【问题描述】:我正在尝试异步显示一个进度表,说明应用程序正在运行,而实际应用程序正在运行。
如下this question,我有以下几点:
主窗体:
public partial class MainForm : Form
public MainForm()
InitializeComponent();
async Task<int> LoadDataAsync()
await Task.Delay(2000);
return 42;
private async void Run_Click(object sender, EventArgs e)
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
进度表
public partial class RunningForm : Form
private readonly SynchronizationContext synchronizationContext;
public RunningForm()
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
public async void ShowRunning()
this.RunningLabel.Text = "Running";
int dots = 0;
await Task.Run(() =>
while (true)
UpadateUi($"Runningnew string('.', dots)");
Thread.Sleep(300);
dots = (dots == 3) ? 0 : dots + 1;
);
public void UpadateUi(string text)
synchronizationContext.Post(
new SendOrPostCallback(o =>
this.RunningLabel.Text = text;
),
text);
public void CloseThread()
synchronizationContext.Post(
new SendOrPostCallback(o =>
this.Close();
),
null);
internal static class DialogExt
public static async Task<DialogResult> ShowDialogAsync(this Form form)
await Task.Yield();
if (form.IsDisposed)
return DialogResult.OK;
return form.ShowDialog();
上面的工作正常,但是当我从另一个人的外面打电话时它不起作用。这是我的控制台应用程序:
class Program
static void Main(string[] args)
new Test().Run();
Console.ReadLine();
class Test
private RunningForm runningForm;
public async void Run()
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.CloseThread();
await progressFormTask;
MessageBox.Show(data.ToString());
async Task<int> LoadDataAsync()
await Task.Delay(2000);
return 42;
观察调试器发生的情况,进程到达await Task.Yield()
并且永远不会进展到return form.ShowDialog()
,因此您永远不会看到RunningForm
。然后进程转到LoadDataAsync()
并永远挂在await Task.Delay(2000)
。
为什么会这样?这与Task
s 的优先级如何(即:Task.Yield()
)有关吗?
【问题讨论】:
我相信您的任务不会自己挂起,而是您的问题是由于尝试在控制台应用程序中建立基于 WinForms 的 UI 而没有设置消息泵(这是WinForms UI 工作)。尝试以与此答案中所示类似的方式实例化和运行您的主表单:***.com/a/277776/2819245。尝试一下,看看它是否能改善(或改变)行为。 另外,由于您使用任务(因此可能使用多个线程),请注意您在程序的主线程中创建消息泵(即执行Application.Run
)而不是在某个后台线程中。
正如@elgonzo 指出的那样,您链接的代码(我是其作者)旨在从带有消息循环的 WinForms UI 线程运行。你说 " "但是当我从另一个人的外面打电话时它不起作用。这是我的控制台应用程序...” 那么,您最终需要从另一个表单还是从非 UI 工作线程运行它?
@noseratio 我不需要它,我更想知道为什么它适用于 WinForms 而不适用于控制台应用程序。
【参考方案1】:
观察调试器发生的情况,进程等待 Task.Yield() 并且永远不会返回 form.ShowDialog() ,因此 你永远不会看到 RunningForm。该过程然后转到 LoadDataAsync() 并在 await Task.Delay(2000) 上永远挂起。
为什么会这样?
这里发生的是,当您在没有任何同步上下文的控制台线程上执行var runningForm = new RunningForm()
时(System.Threading.SynchronizationContext.Current
为空),它会隐式创建WindowsFormsSynchronizationContext
的实例并将其安装在当前线程上,更多关于此here。
然后,当您点击await Task.Yield()
时,ShowDialogAsync
方法将返回给调用者,并且await
延续将发布到新的同步上下文。但是,延续永远不会有机会被调用,因为当前线程没有运行消息循环并且发布的消息没有被抽出。没有死锁,但await Task.Yield()
之后的代码永远不会执行,因此对话框甚至不会显示。 await Task.Delay(2000)
也是如此。
我更感兴趣的是了解为什么它适用于 WinForms 而不是 控制台应用程序。
您的控制台应用程序中需要一个带有消息循环的 UI 线程。尝试像这样重构您的控制台应用程序:
public void Run()
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
;
System.Windows.Forms.Application.Run(runningForm);
这里,Application.Run
的工作是启动一个模态消息循环(并在当前线程上安装WindowsFormsSynchronizationContext
)然后显示表单。 runningForm.Loaded
async 事件处理程序在该同步上下文中调用,因此其中的逻辑应该按预期工作。
然而,这使得Test.Run
一种同步方法,即。例如,它仅在表单关闭且消息循环结束时返回。如果这不是你想要的,你必须创建一个单独的线程来运行你的消息循环,就像我做的with MessageLoopApartment
here。
也就是说,在典型的 WinForms 或 WPF 应用程序中,您几乎不需要辅助 UI 线程。
【讨论】:
以上是关于在用户界面和控制台应用程序中使用 Task.Yield() 的区别的主要内容,如果未能解决你的问题,请参考以下文章