【问题标题】:How to update UI from async routine without BeginInvoke or Invoke [duplicate]如何在没有 BeginInvoke 或 Invoke 的情况下从异步例程更新 UI [重复]
【发布时间】:2019-05-22 17:43:48
【问题描述】:

我看到他们使用 BeginInvoke 从另一个线程更新 UI 的许多代码。是否可以在没有 BeginInvoke 的情况下从异步函数更新 UI?

private async void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            var count = 0;

            await Task.Run(() =>
            {
                for (var i = 0; i <= 500; i++)
                {
                    count = i;
                    BeginInvoke((Action)(() =>
                    {
                        label1.Text = i.ToString();

                    }));
                   Thread.Sleep(100);
                }
            });

            label1.Text = @"Counter " + count;
            button1.Enabled = true;
        }

编辑

请参阅我从链接中获得的以下代码,该链接表明,如果没有 BeginInvoke,我们可以在使用 task.run 时更新 UI。

private readonly SynchronizationContext synchronizationContext;
private DateTime previousTime = DateTime.Now;

public Form1()
{
    InitializeComponent();
    synchronizationContext = SynchronizationContext.Current;
}

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    var count = 0;

    await Task.Run(() =>
    {
        for (var i = 0; i <= 5000000; i++)
        {
            UpdateUI(i);
            count = i;
        }
    });

    label1.Text = @"Counter " + count;
    button1.Enabled = true;
}

public void UpdateUI(int value)
{
    var timeNow = DateTime.Now;

    if ((DateTime.Now - previousTime).Milliseconds <= 50) return;

    synchronizationContext.Post(new SendOrPostCallback(o =>
    {
        label1.Text = @"Counter " + (int)o;
    }), value);             

    previousTime = timeNow;
} 

那么告诉我 synchronizationContext 和 BeginInvoke 两者是一样的吗?应该使用哪一个来从不同的线程更新 UI?哪个效率最高?

请指导我,我是异步/等待和任务用法的新手。

【问题讨论】:

  • 创建一个静态webmethod并在时间间隔内用ajax调用它。
  • 带 Invoke 的异步代码或不带 Invoke 的同步代码。您不能从另一个线程修改 UI 对象。
  • 你为什么要避免(Begin)Invoke?这些方法是专门为使进程间通信更容易而设计的。我想,你可以看看Task 类的ConfigureAwait 方法——它处理执行上下文,对你有帮助。
  • @CamiloTerevinto - 异步并不意味着线程化。
  • @HenkHolterman 你说得对,评论不是最好的。 OP is 明确使用多线程

标签: c# winforms


【解决方案1】:

使用Progress 类。

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    var count = 0;

    // The Progress<T> constructor captures UI context,
    // so the lambda will be run on the UI thread.
    IProgress<int> progress = new Progress<int>(value =>
    {
        label1.Text = value.ToString();
    });

    await Task.Run(() =>
    {
        for (var i = 0; i <= 500; i++)
        {
            count = i;
            progress.Report(i);
            Thread.Sleep(100);
        }
    });

    label1.Text = @"Counter " + count;
    button1.Enabled = true;
}

【讨论】:

  • 你还有一个Task.Run来执行Thread.Sleep()
  • @HenkHolterman - 我认为 OP 在实际代码中而不是在睡眠中进行 CPU 密集型计算。
  • 也许吧。也许不是。
  • 请查看我的编辑部分并再次指导我获取新的示例代码。
【解决方案2】:

要避免的是Task.Run()。当你管理它时,你将不需要 [Begin]Invoke()。

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    var count = 0;

    for (var i = 0; i <= 500; i++)
    {
        count = i;          
        label1.Text = i.ToString();
        await Task.Delay(100);         // do async I/O or Task.Run() here
    }    
}

【讨论】:

  • 请查看我的编辑部分并再次指导我获取新的示例代码。
【解决方案3】:

这取决于您使用的是哪种异步方法。例如,访问外部资源(数据库、Web 服务、文件系统等)的异步方法将在同一个线程上执行,您无需为Invoke 操心。

private async Task UpdateDatabase()
{
    using (var connection = new SqlConnection(connectionString))
    using (var command = connection.CreateCommand())
    {
        command.CommandText = "SELECT Id FROM Table";
        await connection.OpenAsync();
        using (var reader = await command.ExecuteReaderAsync())
        {
            var rowsCount = 0;
            // Since execution will happen on same thread 
            // you will be able update UI controls.
            Label1.Text = $"Rows: {rowsCount}";

            while (await reader.ReadAsync())
            {
                rowsCount ++;
                Label1.Text = $"Rows: {rowsCount}";
            }
        }
    }
}

private async void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;

    await UpdateDatabase();

    button1.Enabled = true;
}

对于在其他线程上执行的方法,最佳做法是在不“接触” UI 控件的情况下执行这些方法,而是将方法的结果返回给主线程,然后更新 UI。

对于您希望使用“进度”信息更新 UI 的特定情况,您可以使用 BackgroundWorker 类或已经提到的 Progress 类。

【讨论】:

  • 请查看我的编辑部分并再次指导我获取新的示例代码。
猜你喜欢
  • 1970-01-01
  • 2019-12-16
  • 2019-05-02
  • 1970-01-01
  • 2019-12-16
  • 2017-03-05
  • 2019-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多