当 BeginInvokes 发送垃圾邮件时,Await 永远不会在 UI 线程上收到回调

问题描述 投票:0回答:1

我最近一直在开发一些库,我偶然发现了一个困扰我的问题,因为我似乎找不到发生这种情况的原因。

我举个例子:

public partial class Form1 : Form
{
    TaskCompletionSource<bool> _completionTask;

    public Form1()
    {
        InitializeComponent();
    }

    //When we click a button, this happens.
    private async void start_Click(object sender, EventArgs e)
    {
        _completionTask = new TaskCompletionSource<bool>();
        _ = Task.Run(() => Work());
        await WaitForCompletion();
        Debug.WriteLine("Successfully Left Await!");
    }

    private void Work()
    {
        for (int i = 0; i < 100000; i++)
        {
            BeginInvoke(() =>
            {
                label1.Text = i.ToString();
            });
        }

        _completionTask.SetResult(true);
    }

    private async Task WaitForCompletion()
    {
        await _completionTask.Task;
    }
}

我们这里有一个 Windows 窗体内非常简单的代码。我们有一个在单击按钮时运行的方法。此方法运行“Task.Run()”内的“Work”方法,然后等待“_completionTask”任务。

在整个过程结束时,我们应该在控制台中看到调试“成功离开等待!”。有趣的是 - 它永远不会到达它......我猜这是因为“BeginInvoke”垃圾邮件使 UI 线程太忙?如果将“BeginInvoke”更改为“Invoke”,它就会起作用。同样,您可以输入 '_completionTask.SetResult(true);'进入“BeginInvoke”,它也会起作用!

我的问题是 - 有谁真正知道这背后的真正原因,也许有一些关于如何正确处理这种情况的解释?

c# winforms asynchronous
1个回答
0
投票

async/await
取代
BeginInvoke
的需要。也没有理由使用 TaskCompletionSource。如果一个方法需要执行一些繁重的任务但仍然更新 UI,它应该使用例如
Task.Run
执行异步部分并等待任务。
await
执行返回到 UI 线程后,可以更新 GUI。

这意味着问题的代码可以替换为:

private async void start_Click(object sender, EventArgs e)
{
    await DoWork();
    Debug.WriteLine("Successfully Left Await!");
}

private async Task DoWork()
{
    for (int i = 0; i < 100000; i++)
    {
        await Task.Run(()=>DoSomeStuff());
        label1.Text = i.ToString();
    }
}

或者,如果实际工作是异步的,例如进行 HTTP 调用:

private async Task DoWork()
{
    for (int i = 0; i < 100000; i++)
    {
        var message=await httpClient.GetStringAsync(url);
        label1.Text = message;
    }
}

另一种选择是使用 Progress 任务从后台线程(例如从对 UI 一无所知的库)回调 UI。顾名思义,它通常用于报告进度。回调在创建

Progress<T>
对象的线程上执行,但任何线程都可以调用
Report
方法:

private async void start_Click(object sender, EventArgs e)
{
    var progress=new Progress<string>(i=>label1.Text=i.ToString());

    await Task.Run(()=>DoWork(progress);

    Debug.WriteLine("Successfully Left Await!");
}

private async Task DoWork(IProgress<int> progress)
{
    for (int i = 0; i < 100000; i++)
    {
        DoSomeStuff();
        progress.Report(i);
    }
}

在这种情况下,整个

DoWork
方法在后台运行。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.