问题
Just created a WPF project .net 4.6
And have put this code inside
lbl1
is a label on the GUI
But the label is never updated or the while loop continue only 1 time
private void Button_Click_1(object sender, RoutedEventArgs e)
{
var t = Task.Run(
async () =>
{
await AsyncLoop();
});
}
async Task AsyncLoop()
{
while (true)
{
string result = await LoadNextItem();
lbl1.Content = result;
}
}
private static int ir11 = 0;
async Task<string> LoadNextItem()
{
ir11++;
return "aa " + ir11;
}
回答1:
Please user Application.Current.Dispatcher.Invoke to access the ui thread
async Task AsyncLoop()
{
while (true)
{
string result = await LoadNextItem();
Application.Current.Dispatcher.Invoke(new Action(() => { lbl1.Content = result; }));
}
}
回答2:
The C# compiler is giving you a warning that tells you what the problem is.
Specifically, this method is not asynchronous:
async Task<string> LoadNextItem()
{
ir11++;
return "aa " + ir11;
}
The compiler message will inform you that this async
method has no await
statements, and thus will run synchronously. You should only use async
where it makes sense, usually for I/O-based operations, e.g.:
async Task<string> LoadNextItemAsync()
{
await Task.Delay(100); // Placeholder for actual asynchronous work.
ir11++;
return "aa " + ir11;
}
Alternatively, if you don't have asynchronous operations, but rather you have some tight CPU-bound loops, then you can push those off to a background thread via Task.Run
:
string LoadNextItem()
{
ir11++;
return "aa " + ir11;
}
while (true)
{
string result = await Task.Run(() => LoadNextItem());
lbl1.Content = result;
}
回答3:
By invoking Task.Run you broke your association(SynchronizationContext) with the GUI thread (or WPF Dispatcher) and lost most of the async/await 'goodness'.
Why not use an async void event handler and just come back to the SynchronizationContext(GUI Thread/Dispatcher) for each step?
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
while (true)
{
string result = await LoadNextItem();
lbl1.Content = result;
}
}
private static int ir11 = 0;
Task<string> LoadNextItem()
{
await Task.Delay(1000); // placeholder for actual async work
ir11++;
return "aa " + ir11;
}
Or if you really want to separate the state machine for the 'on-going' operations, try passing an IProgress<T>
(the default impl. Progress<T>
or specifically Progress<string>
should work great in this case). See this article by @Stephen Cleary
His example is very close to what you stated in the question. I've copied it here for SO independence.
public async void StartProcessingButton_Click(object sender, EventArgs e)
{
// The Progress<T> constructor captures our UI context,
// so the lambda will be run on the UI thread.
var progress = new Progress<int>(percent =>
{
textBox1.Text = percent + "%";
});
// DoProcessing is run on the thread pool.
await Task.Run(() => DoProcessing(progress));
textBox1.Text = "Done!";
}
public void DoProcessing(IProgress<int> progress)
{
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
if (progress != null)
progress.Report(i);
}
}
Edit: I must admit, while Progress<T>
is a nice abstraction, in this case it is just going to fall down to Dispatcher.Invoke as @Pamparanpa suggested.
来源:https://stackoverflow.com/questions/38638911/how-to-update-gui-continuously-with-async