我看到许多代码,他们使用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?哪个效率最高?
请指导我,我是异步/等待和任务用法的新手。
答案 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
类。