如何在没有BeginInvoke或Invoke的情况下从异步例程更新UI

时间:2018-12-22 19:06:32

标签: c# winforms

我看到许多代码,他们使用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?哪个效率最高?

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

3 个答案:

答案 0 :(得分:2)

使用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;
}

答案 1 :(得分: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
    }    
}

答案 2 :(得分:1)

这取决于您使用哪种异步方法。例如,访问外部资源(数据库,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类。