在用户界面和控制台应用程序中使用Task.Yield()之间的区别 [英] Difference between using Task.Yield() in a User Interface and Console App

查看:117
本文介绍了在用户界面和控制台应用程序中使用Task.Yield()之间的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试异步显示一个进度表,其中说应用程序正在运行,而实际应用程序正在运行.

I'm trying to asynchronously show a progress form that says the application is running while the actual application is running.

按照以下此问题,我有以下内容:

As following this question, I have the following:

主要形式:

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($"Running{new 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();
    }
}

上面的方法工作正常,但是当我从另一个外部进行呼叫时却无法正常工作.这是我的控制台应用程序:

The above works fine, but it doesn't work when I'm calling from outside of another from. This is my console app:

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)上.

Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).

为什么会这样?它与 Task 的优先级是否有关系(即:

Why is this happening? Does it have something to do with how Tasks are prioritized (ie: Task.Yield())?

推荐答案

看着调试器发生了什么,过程就在等待着Task.Yield()永远不会返回form.ShowDialog(),因此您永远不会看到RunningForm.然后,该过程转到LoadDataAsync()并永久挂起,等待Task.Delay(2000).

Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).

为什么会这样?

这里发生的是,当您在没有任何同步上下文的情况下在控制台线程上执行 var runningForm = new RunningForm()时( System.Threading.SynchronizationContext.Current 为null),则隐式创建 WindowsFormsSynchronizationContext 的实例并将其安装在当前线程上,有关更多信息,请参见此处.

What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.

然后,当您点击 await Task.Yield()时, ShowDialogAsync 方法将返回给调用方,并且将 await 的延续发布到新的同步上下文.但是,继续操作永远不会有机会被调用,因为当前线程不会运行消息循环,并且发布的消息也不会被泵送.没有死锁,但是 await Task.Yield()之后的代码从未执行,因此对话框甚至不会显示.关于 await Task.Delay(2000).

Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).

我对了解为什么它适用于WinForms而不适用于WinForms更感兴趣.控制台应用程序.

I'm more interested in learning why it works for WinForms and not for Console Applications.

您需要在控制台应用程序中使用带有消息循环的UI线程.尝试像这样重构您的控制台应用程序:

You need a UI thread with a message loop in your console app. Try refactoring your console app like this:

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 异步事件处理程序,因此其中的逻辑应按预期工作.

Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.

但是,这使 Test.Run 是一种同步方法,即例如,仅在关闭表单且消息循环结束时才返回.如果这不是您想要的,则必须创建一个单独的线程来运行消息循环,就像我使用 >此处是MessageLoopApartment .

That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.

也就是说,在典型的WinForms或WPF应用程序中,您几乎永远不需要辅助UI线程.

That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.

这篇关于在用户界面和控制台应用程序中使用Task.Yield()之间的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆