I have a WPF GUI, where I want to press a button to start a long task without freezing the window for the duration of the task. While the task is running I would like to get reports on progress, and I would like to incorporate another button that will stop the task at any time I choose.
我有一个WPF GUI,在这里我想按一个按钮来启动一个长任务,而不会在任务期间冻结窗口。当任务正在运行时,我想获得有关进度的报告,我想在我选择的任何时候添加另一个按钮来停止任务。
I cannot figure the correct way to use async/await/task. I can't include everything I've tried, but this is what I have at the moment.
我无法想出使用async / await / task的正确方法。我不能包括我尝试过的所有东西,但这就是我现在拥有的东西。
A WPF window class :
一个WPF窗口类:
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
internal bool StopWorking = false;
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
Task burnTheBaby = _burnBabyBurn.ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);
await burnTheBaby;
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
StopWorking = true;
}
//A method to allow the worker method to call back and update the gui
internal void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
And a class for the worker method:
还有一个worker方法的类:
class OtherClass
{
internal Task ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
var tcs = new TaskCompletionSource<int>();
//Start doing work
gui.UpdateWindow("Work Started");
While(stillWorking)
{
//Mid procedure progress report
gui.UpdateWindow("Bath water n% thrown out");
if (gui.StopTraining) return tcs.Task;
}
//Exit message
gui.UpdateWindow("Done and Done");
return tcs.Task;
}
}
This runs, but the WPF function window is still blocked once the worker method starts.
这会运行,但是一旦worker方法启动,WPF函数窗口仍会被阻止。
I need to know how to arrange the async/await/task declarations to allow
我需要知道如何安排async / await / task声明来允许
A) the worker method to not block the gui window
B) let the worker method update the gui window
C) allow the gui window to stop interrupt and stop the worker method
A)不阻止gui窗口的worker方法B)让worker方法更新gui窗口C)允许gui窗口停止中断并停止worker方法
Any help or pointers are much appreciated.
任何帮助或指针都非常感谢。
5 个解决方案
#1
30
Quick hint:
快速提示:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
txt.Text = "started";
await Task.Run(()=> HeavyMethod(this));
txt.Text = "done";
}
internal void HeavyMethod(MainWindow gui)
{
while (stillWorking)
{
UpdateGUIMethod(gui, ".");
System.Threading.Thread.Sleep(51);
}
}
void UpdateGUIMethod(MainWindow gui, string text)
{
gui.Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Explanation:
-
async
andawait
must be used on the same method.必须在同一方法上使用async和await。
-
Task.Run
queues a method (as aTask
orTask<T>
) in the thread pool (it uses/creates another thread to run the task)Task.Run在线程池中排队方法(作为任务或任务
)(它使用/创建另一个线程来运行任务) -
The execution waits at
await
for the task to finish and throws back its results, without blocking the main thread because of theasync
keyword's magic ability.执行等待等待任务完成并抛出其结果,而不会因为async关键字的魔法能力而阻塞主线程。
-
The magic of
async
keyword is that it does not create another thread. It only enables the compiler to give up and take back the control over that method.异步关键字的神奇之处在于它不会创建另一个线程。它只允许编译器放弃并收回对该方法的控制。
So
所以
Your main thread calls the async
method (Button_Click_3
) like a normal method and no threading so far... Now you can run a task inside the Button_Click_3
like this:
您的主线程调用异步方法(Button_Click_3)就像普通方法一样,到目前为止还没有线程...现在您可以在Button_Click_3中运行任务,如下所示:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
Task task = Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
//wait for it to end without blocking the main thread
await task;
}
or simply
或简单地说
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}
or if ExecuteLongProcedureAsync
has a return value of type string
或者,如果ExecuteLongProcedureAsync的返回值为string类型
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
Task<string> task = Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
string returnValue = await task;
}
or simply
或简单地说
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
string returnValue = await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
//or in cases where you already have a "Task returning" method:
// var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}
The method inside the task (or ExecuteLongProcedureAsync
) runs asynchronously and looks like this:
任务内部的方法(或ExecuteLongProcedureAsync)以异步方式运行,如下所示:
//change the value for the following flag to terminate the loop
bool stillWorking = true;
//calling this method blocks the calling thread
//you must run a task for it
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
//Start doing work
gui.UpdateWindow("Work Started");
while (stillWorking)
{
//put a dot in the window showing the progress
gui.UpdateWindow(".");
//the following line will block main thread unless
//ExecuteLongProcedureAsync is called with await keyword
System.Threading.Thread.Sleep(51);
}
gui.UpdateWindow("Done and Done");
}
Note 1:
Task.Run
is newer (.NetFX4.5) and simpler version of Task.Factory.StartNew
Task.Run是较新的(.NetFX4.5)和更简单的Task.Factory.StartNew版本
await
is not Task.Wait()
await不是Task.Wait()
Note 2:
Sleep
blocks the main thread even that it is called in a method with async
keyword.
即使在使用async关键字的方法中调用它,Sleep也会阻塞主线程。
await
prevents the task from blocking the main thread because of the async
keyword.
await阻止任务因async关键字而阻塞主线程。
private async void Button_Click(object sender, RoutedEventArgs e)
{
ExecuteLongProcedureAsync();//blocks
await Task.Run(() => ExecuteLongProcedureAsync());//does not block
}
Note 3 (GUI):
If you have to access GUI asynchronously (inside ExecuteLongProcedureAsync
method), invoke any operation which involves accessing to GUI fields:
如果必须异步访问GUI(在ExecuteLongProcedureAsync方法内),请调用涉及访问GUI字段的任何操作:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
However, If a task is started as a result of a property changed callback from the ViewModel, there is no need to use Dispatcher.Invoke
because the callback is actually executed from the UI thread.
但是,如果由于属性从ViewModel更改回调而启动任务,则无需使用Dispatcher.Invoke,因为回调实际上是从UI线程执行的。
Accessing collections on non-UI Threads
在非UI线程*问集合
WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.
WPF使您可以访问和修改除创建集合之外的线程上的数据集合。这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据。通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互。
Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.
由INotifyPropertyChanged触发的值更改会自动编组回调度程序。
How to enable cross-thread access
如何启用跨线程访问
Remember, async
method itself runs on the main thread. So this is valid:
请记住,异步方法本身在主线程上运行。所以这是有效的:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
txt.Text = "starting";
await Task.Run(()=> ExecuteLongProcedureAsync1());
txt.Text = "waiting";
await Task.Run(()=> ExecuteLongProcedureAsync2());
txt.Text = "finished";
}
Read more
MSDN解释了Task
MSDN解释了异步
async await
- Behind the scenes
异步等待 - 在幕后
async await - 常见问题解答
You may also read a simple asynchronous file writer to know where you should concurrent.
您还可以阅读一个简单的异步文件编写器,以了解应该并发的位置。
Investigate concurrent namespace
调查并发命名空间
Finally read this e-book: Patterns_of_Parallel_Programming_CSharp
最后阅读这本电子书:Patterns_of_Parallel_Programming_CSharp
#2
6
Your use of TaskCompletionSource<T>
is incorrect. TaskCompletionSource<T>
is a way to create TAP-compatible wrappers for asynchronous operations. In your ExecuteLongProcedureAsync
method, the sample code is all CPU-bound (i.e., inherently synchronous, not asynchronous).
您对TaskCompletionSource
So, it's much more natural to write ExecuteLongProcedure
as a synchronous method. It's also a good idea to use standard types for standard behaviors, in particular using IProgress<T>
for progress updates and CancellationToken
for cancellation:
因此,将ExecuteLongProcedure编写为同步方法更为自然。对标准行为使用标准类型也是一个好主意,特别是使用IProgress
internal void ExecuteLongProcedure(int param1, int param2, int param3,
CancellationToken cancellationToken, IProgress<string> progress)
{
//Start doing work
if (progress != null)
progress.Report("Work Started");
while (true)
{
//Mid procedure progress report
if (progress != null)
progress.Report("Bath water n% thrown out");
cancellationToken.ThrowIfCancellationRequested();
}
//Exit message
if (progress != null)
progress.Report("Done and Done");
}
Now you have a more reusable type (no GUI dependencies) that uses the appropriate conventions. It can be used as such:
现在,您有一个使用适当约定的更可重用的类型(没有GUI依赖项)。它可以这样使用:
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
CancellationTokenSource _stopWorkingCts = new CancellationTokenSource();
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
var progress = new Progress<string>(data => UpdateWindow(data));
try
{
await Task.Run(() => _burnBabyBurn.ExecuteLongProcedure(intParam1, intParam2, intParam3,
_stopWorkingCts.Token, progress));
}
catch (OperationCanceledException)
{
// TODO: update the GUI to indicate the method was canceled.
}
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
_stopWorkingCts.Cancel();
}
//A method to allow the worker method to call back and update the gui
void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
#3
4
This is a simplified version of the most popular answer here by Bijan. I simplified Bijan's answer to help me think through the problem using the nice formatting provided by Stack Overflow.
这是Bijan最受欢迎的答案的简化版本。我简化了Bijan的答案,帮助我使用Stack Overflow提供的漂亮格式来解决问题。
By carefully reading and editing Bijan's post I finally understood: How to wait for async method to complete?
通过仔细阅读和编辑Bijan的帖子我终于明白了:如何等待异步方法完成?
In my case the chosen answer for that other post is what ultimately led me to solve my problem:
在我的情况下,另一篇文章的选择答案最终导致我解决了我的问题:
"Avoid async void
. Have your methods return Task
instead of void
. Then you can await
them."
“避免异步void。让你的方法返回Task而不是void。然后你可以等待它们。”
My simplified version of Bijan's (excellent) answer follows:
我的简化版Bijan(优秀)答案如下:
1) This starts a task using async and await:
1)这将使用async和await启动任务:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
// if ExecuteLongProcedureAsync has a return value
var returnValue = await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}
2) This is the method to execute asynchronously:
2)这是异步执行的方法:
bool stillWorking = true;
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
//Start doing work
gui.UpdateWindow("Work Started");
while (stillWorking)
{
//put a dot in the window showing the progress
gui.UpdateWindow(".");
//the following line blocks main thread unless
//ExecuteLongProcedureAsync is called with await keyword
System.Threading.Thread.Sleep(50);
}
gui.UpdateWindow("Done and Done");
}
3) Invoke the operation which involves a property from gui:
3)调用涉及gui属性的操作:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Or,
要么,
void UpdateWindow(string text)
{
//simply
txt.Text += text;
}
Closing comments) In most cases you have two methods.
结束评论)在大多数情况下,您有两种方法。
-
First method (
Button_Click_3
) calls the second method and has theasync
modifier which tells the compiler to enable threading for that method.第一种方法(Button_Click_3)调用第二种方法,并具有async修饰符,告诉编译器为该方法启用线程。
-
Thread.Sleep
in anasync
method blocks the main thread. but awaiting a task does not. - 异步方法中的Thread.Sleep阻塞主线程。但等待任务却没有。
- Execution stops on current thread (second thread) on
await
statements until task is finished. - 执行在await语句的当前线程(第二个线程)上停止,直到任务完成。
- You can't use
await
outside anasync
method - 您不能在异步方法之外使用等待
-
-
Second method (
ExecuteLongProcedureAsync
) is wrapped within a task and returns a genericTask<original return type>
object which can be instructed to be processed asynchronously by addingawait
before it.第二个方法(ExecuteLongProcedureAsync)包装在一个任务中,并返回一个通用的Task
对象,可以通过在它之前添加await来指示异步处理。 - Everything in this method in executed asynchronously
- 此方法中的所有内容都是异步执行的
Important:
Liero brought up an important issue. When you are Binding an element to a ViewModel property, the property changed callback is executed in UI thread. So there is no need to use Dispatcher.Invoke
. Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.
列罗提出了一个重要问题。将元素绑定到ViewModel属性时,将在UI线程中执行属性更改的回调。所以不需要使用Dispatcher.Invoke。由INotifyPropertyChanged触发的值更改会自动编组回调度程序。
#4
2
Here is an example using async/await
, IProgress<T>
and CancellationTokenSource
. These are the modern C# and .Net Framework language features that you should be using. The other solutions are making my eyes bleed a bit.
以下是使用async / await,IProgress
Code Features
- Count to 100 over a period of 10 seconds
- 在10秒的时间内计数到100
- Display progress on a progress bar
- 显示进度条上的进度
- Long running work (a 'wait' period) performed without blocking the UI
- 在不阻止UI的情况下执行长时间运行的工作(“等待”期间)
- User triggered cancellation
- 用户触发取消
- Incremental progress updates
- 增量进度更新
- Post operation status report
- 发布操作状态报告
The view
<Window x:Class="ProgressExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight" Height="93.258" Width="316.945">
<StackPanel>
<Button x:Name="Button_Start" Click="Button_Click">Start</Button>
<ProgressBar x:Name="ProgressBar_Progress" Height="20" Maximum="100"/>
<Button x:Name="Button_Cancel" IsEnabled="False" Click="Button_Cancel_Click">Cancel</Button>
</StackPanel>
</Window>
The code
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private CancellationTokenSource currentCancellationSource;
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
// Enable/disabled buttons so that only one counting task runs at a time.
this.Button_Start.IsEnabled = false;
this.Button_Cancel.IsEnabled = true;
try
{
// Set up the progress event handler - this instance automatically invokes to the UI for UI updates
// this.ProgressBar_Progress is the progress bar control
IProgress<int> progress = new Progress<int>(count => this.ProgressBar_Progress.Value = count);
currentCancellationSource = new CancellationTokenSource();
await CountToOneHundredAsync(progress, this.currentCancellationSource.Token);
// Operation was successful. Let the user know!
MessageBox.Show("Done counting!");
}
catch (OperationCanceledException)
{
// Operation was cancelled. Let the user know!
MessageBox.Show("Operation cancelled.");
}
finally
{
// Reset controls in a finally block so that they ALWAYS go
// back to the correct state once the counting ends,
// regardless of any exceptions
this.Button_Start.IsEnabled = true;
this.Button_Cancel.IsEnabled = false;
this.ProgressBar_Progress.Value = 0;
// Dispose of the cancellation source as it is no longer needed
this.currentCancellationSource.Dispose();
this.currentCancellationSource = null;
}
}
private async Task CountToOneHundredAsync(IProgress<int> progress, CancellationToken cancellationToken)
{
for (int i = 1; i <= 100; i++)
{
// This is where the 'work' is performed.
// Feel free to swap out Task.Delay for your own Task-returning code!
// You can even await many tasks here
// ConfigureAwait(false) tells the task that we dont need to come back to the UI after awaiting
// This is a good read on the subject - https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
// If cancelled, an exception will be thrown by the call the task.Delay
// and will bubble up to the calling method because we used await!
// Report progress with the current number
progress.Report(i);
}
}
private void Button_Cancel_Click(object sender, RoutedEventArgs e)
{
// Cancel the cancellation token
this.currentCancellationSource.Cancel();
}
}
#5
-2
It has been several years since the question was asked but I think it is worth noting that the BackgroundWorker class is designed precisely to achieve A, B and C requirements.
问题被问到已经有好几年了,但我认为值得注意的是,BackgroundWorker类的设计正是为了达到A,B和C的要求。
Complete sample in msdn reference page: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx
msdn参考页面中的完整示例:https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx
#1
30
Quick hint:
快速提示:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
txt.Text = "started";
await Task.Run(()=> HeavyMethod(this));
txt.Text = "done";
}
internal void HeavyMethod(MainWindow gui)
{
while (stillWorking)
{
UpdateGUIMethod(gui, ".");
System.Threading.Thread.Sleep(51);
}
}
void UpdateGUIMethod(MainWindow gui, string text)
{
gui.Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Explanation:
-
async
andawait
must be used on the same method.必须在同一方法上使用async和await。
-
Task.Run
queues a method (as aTask
orTask<T>
) in the thread pool (it uses/creates another thread to run the task)Task.Run在线程池中排队方法(作为任务或任务
)(它使用/创建另一个线程来运行任务) -
The execution waits at
await
for the task to finish and throws back its results, without blocking the main thread because of theasync
keyword's magic ability.执行等待等待任务完成并抛出其结果,而不会因为async关键字的魔法能力而阻塞主线程。
-
The magic of
async
keyword is that it does not create another thread. It only enables the compiler to give up and take back the control over that method.异步关键字的神奇之处在于它不会创建另一个线程。它只允许编译器放弃并收回对该方法的控制。
So
所以
Your main thread calls the async
method (Button_Click_3
) like a normal method and no threading so far... Now you can run a task inside the Button_Click_3
like this:
您的主线程调用异步方法(Button_Click_3)就像普通方法一样,到目前为止还没有线程...现在您可以在Button_Click_3中运行任务,如下所示:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
Task task = Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
//wait for it to end without blocking the main thread
await task;
}
or simply
或简单地说
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}
or if ExecuteLongProcedureAsync
has a return value of type string
或者,如果ExecuteLongProcedureAsync的返回值为string类型
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
Task<string> task = Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
string returnValue = await task;
}
or simply
或简单地说
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
string returnValue = await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
//or in cases where you already have a "Task returning" method:
// var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}
The method inside the task (or ExecuteLongProcedureAsync
) runs asynchronously and looks like this:
任务内部的方法(或ExecuteLongProcedureAsync)以异步方式运行,如下所示:
//change the value for the following flag to terminate the loop
bool stillWorking = true;
//calling this method blocks the calling thread
//you must run a task for it
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
//Start doing work
gui.UpdateWindow("Work Started");
while (stillWorking)
{
//put a dot in the window showing the progress
gui.UpdateWindow(".");
//the following line will block main thread unless
//ExecuteLongProcedureAsync is called with await keyword
System.Threading.Thread.Sleep(51);
}
gui.UpdateWindow("Done and Done");
}
Note 1:
Task.Run
is newer (.NetFX4.5) and simpler version of Task.Factory.StartNew
Task.Run是较新的(.NetFX4.5)和更简单的Task.Factory.StartNew版本
await
is not Task.Wait()
await不是Task.Wait()
Note 2:
Sleep
blocks the main thread even that it is called in a method with async
keyword.
即使在使用async关键字的方法中调用它,Sleep也会阻塞主线程。
await
prevents the task from blocking the main thread because of the async
keyword.
await阻止任务因async关键字而阻塞主线程。
private async void Button_Click(object sender, RoutedEventArgs e)
{
ExecuteLongProcedureAsync();//blocks
await Task.Run(() => ExecuteLongProcedureAsync());//does not block
}
Note 3 (GUI):
If you have to access GUI asynchronously (inside ExecuteLongProcedureAsync
method), invoke any operation which involves accessing to GUI fields:
如果必须异步访问GUI(在ExecuteLongProcedureAsync方法内),请调用涉及访问GUI字段的任何操作:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
However, If a task is started as a result of a property changed callback from the ViewModel, there is no need to use Dispatcher.Invoke
because the callback is actually executed from the UI thread.
但是,如果由于属性从ViewModel更改回调而启动任务,则无需使用Dispatcher.Invoke,因为回调实际上是从UI线程执行的。
Accessing collections on non-UI Threads
在非UI线程*问集合
WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.
WPF使您可以访问和修改除创建集合之外的线程上的数据集合。这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据。通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互。
Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.
由INotifyPropertyChanged触发的值更改会自动编组回调度程序。
How to enable cross-thread access
如何启用跨线程访问
Remember, async
method itself runs on the main thread. So this is valid:
请记住,异步方法本身在主线程上运行。所以这是有效的:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
txt.Text = "starting";
await Task.Run(()=> ExecuteLongProcedureAsync1());
txt.Text = "waiting";
await Task.Run(()=> ExecuteLongProcedureAsync2());
txt.Text = "finished";
}
Read more
MSDN解释了Task
MSDN解释了异步
async await
- Behind the scenes
异步等待 - 在幕后
async await - 常见问题解答
You may also read a simple asynchronous file writer to know where you should concurrent.
您还可以阅读一个简单的异步文件编写器,以了解应该并发的位置。
Investigate concurrent namespace
调查并发命名空间
Finally read this e-book: Patterns_of_Parallel_Programming_CSharp
最后阅读这本电子书:Patterns_of_Parallel_Programming_CSharp
#2
6
Your use of TaskCompletionSource<T>
is incorrect. TaskCompletionSource<T>
is a way to create TAP-compatible wrappers for asynchronous operations. In your ExecuteLongProcedureAsync
method, the sample code is all CPU-bound (i.e., inherently synchronous, not asynchronous).
您对TaskCompletionSource
So, it's much more natural to write ExecuteLongProcedure
as a synchronous method. It's also a good idea to use standard types for standard behaviors, in particular using IProgress<T>
for progress updates and CancellationToken
for cancellation:
因此,将ExecuteLongProcedure编写为同步方法更为自然。对标准行为使用标准类型也是一个好主意,特别是使用IProgress
internal void ExecuteLongProcedure(int param1, int param2, int param3,
CancellationToken cancellationToken, IProgress<string> progress)
{
//Start doing work
if (progress != null)
progress.Report("Work Started");
while (true)
{
//Mid procedure progress report
if (progress != null)
progress.Report("Bath water n% thrown out");
cancellationToken.ThrowIfCancellationRequested();
}
//Exit message
if (progress != null)
progress.Report("Done and Done");
}
Now you have a more reusable type (no GUI dependencies) that uses the appropriate conventions. It can be used as such:
现在,您有一个使用适当约定的更可重用的类型(没有GUI依赖项)。它可以这样使用:
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
CancellationTokenSource _stopWorkingCts = new CancellationTokenSource();
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
var progress = new Progress<string>(data => UpdateWindow(data));
try
{
await Task.Run(() => _burnBabyBurn.ExecuteLongProcedure(intParam1, intParam2, intParam3,
_stopWorkingCts.Token, progress));
}
catch (OperationCanceledException)
{
// TODO: update the GUI to indicate the method was canceled.
}
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
_stopWorkingCts.Cancel();
}
//A method to allow the worker method to call back and update the gui
void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
#3
4
This is a simplified version of the most popular answer here by Bijan. I simplified Bijan's answer to help me think through the problem using the nice formatting provided by Stack Overflow.
这是Bijan最受欢迎的答案的简化版本。我简化了Bijan的答案,帮助我使用Stack Overflow提供的漂亮格式来解决问题。
By carefully reading and editing Bijan's post I finally understood: How to wait for async method to complete?
通过仔细阅读和编辑Bijan的帖子我终于明白了:如何等待异步方法完成?
In my case the chosen answer for that other post is what ultimately led me to solve my problem:
在我的情况下,另一篇文章的选择答案最终导致我解决了我的问题:
"Avoid async void
. Have your methods return Task
instead of void
. Then you can await
them."
“避免异步void。让你的方法返回Task而不是void。然后你可以等待它们。”
My simplified version of Bijan's (excellent) answer follows:
我的简化版Bijan(优秀)答案如下:
1) This starts a task using async and await:
1)这将使用async和await启动任务:
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
// if ExecuteLongProcedureAsync has a return value
var returnValue = await Task.Run(()=>
ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}
2) This is the method to execute asynchronously:
2)这是异步执行的方法:
bool stillWorking = true;
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
//Start doing work
gui.UpdateWindow("Work Started");
while (stillWorking)
{
//put a dot in the window showing the progress
gui.UpdateWindow(".");
//the following line blocks main thread unless
//ExecuteLongProcedureAsync is called with await keyword
System.Threading.Thread.Sleep(50);
}
gui.UpdateWindow("Done and Done");
}
3) Invoke the operation which involves a property from gui:
3)调用涉及gui属性的操作:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Or,
要么,
void UpdateWindow(string text)
{
//simply
txt.Text += text;
}
Closing comments) In most cases you have two methods.
结束评论)在大多数情况下,您有两种方法。
-
First method (
Button_Click_3
) calls the second method and has theasync
modifier which tells the compiler to enable threading for that method.第一种方法(Button_Click_3)调用第二种方法,并具有async修饰符,告诉编译器为该方法启用线程。
-
Thread.Sleep
in anasync
method blocks the main thread. but awaiting a task does not. - 异步方法中的Thread.Sleep阻塞主线程。但等待任务却没有。
- Execution stops on current thread (second thread) on
await
statements until task is finished. - 执行在await语句的当前线程(第二个线程)上停止,直到任务完成。
- You can't use
await
outside anasync
method - 您不能在异步方法之外使用等待
-
-
Second method (
ExecuteLongProcedureAsync
) is wrapped within a task and returns a genericTask<original return type>
object which can be instructed to be processed asynchronously by addingawait
before it.第二个方法(ExecuteLongProcedureAsync)包装在一个任务中,并返回一个通用的Task
对象,可以通过在它之前添加await来指示异步处理。 - Everything in this method in executed asynchronously
- 此方法中的所有内容都是异步执行的
Important:
Liero brought up an important issue. When you are Binding an element to a ViewModel property, the property changed callback is executed in UI thread. So there is no need to use Dispatcher.Invoke
. Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.
列罗提出了一个重要问题。将元素绑定到ViewModel属性时,将在UI线程中执行属性更改的回调。所以不需要使用Dispatcher.Invoke。由INotifyPropertyChanged触发的值更改会自动编组回调度程序。
#4
2
Here is an example using async/await
, IProgress<T>
and CancellationTokenSource
. These are the modern C# and .Net Framework language features that you should be using. The other solutions are making my eyes bleed a bit.
以下是使用async / await,IProgress
Code Features
- Count to 100 over a period of 10 seconds
- 在10秒的时间内计数到100
- Display progress on a progress bar
- 显示进度条上的进度
- Long running work (a 'wait' period) performed without blocking the UI
- 在不阻止UI的情况下执行长时间运行的工作(“等待”期间)
- User triggered cancellation
- 用户触发取消
- Incremental progress updates
- 增量进度更新
- Post operation status report
- 发布操作状态报告
The view
<Window x:Class="ProgressExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight" Height="93.258" Width="316.945">
<StackPanel>
<Button x:Name="Button_Start" Click="Button_Click">Start</Button>
<ProgressBar x:Name="ProgressBar_Progress" Height="20" Maximum="100"/>
<Button x:Name="Button_Cancel" IsEnabled="False" Click="Button_Cancel_Click">Cancel</Button>
</StackPanel>
</Window>
The code
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private CancellationTokenSource currentCancellationSource;
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
// Enable/disabled buttons so that only one counting task runs at a time.
this.Button_Start.IsEnabled = false;
this.Button_Cancel.IsEnabled = true;
try
{
// Set up the progress event handler - this instance automatically invokes to the UI for UI updates
// this.ProgressBar_Progress is the progress bar control
IProgress<int> progress = new Progress<int>(count => this.ProgressBar_Progress.Value = count);
currentCancellationSource = new CancellationTokenSource();
await CountToOneHundredAsync(progress, this.currentCancellationSource.Token);
// Operation was successful. Let the user know!
MessageBox.Show("Done counting!");
}
catch (OperationCanceledException)
{
// Operation was cancelled. Let the user know!
MessageBox.Show("Operation cancelled.");
}
finally
{
// Reset controls in a finally block so that they ALWAYS go
// back to the correct state once the counting ends,
// regardless of any exceptions
this.Button_Start.IsEnabled = true;
this.Button_Cancel.IsEnabled = false;
this.ProgressBar_Progress.Value = 0;
// Dispose of the cancellation source as it is no longer needed
this.currentCancellationSource.Dispose();
this.currentCancellationSource = null;
}
}
private async Task CountToOneHundredAsync(IProgress<int> progress, CancellationToken cancellationToken)
{
for (int i = 1; i <= 100; i++)
{
// This is where the 'work' is performed.
// Feel free to swap out Task.Delay for your own Task-returning code!
// You can even await many tasks here
// ConfigureAwait(false) tells the task that we dont need to come back to the UI after awaiting
// This is a good read on the subject - https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
// If cancelled, an exception will be thrown by the call the task.Delay
// and will bubble up to the calling method because we used await!
// Report progress with the current number
progress.Report(i);
}
}
private void Button_Cancel_Click(object sender, RoutedEventArgs e)
{
// Cancel the cancellation token
this.currentCancellationSource.Cancel();
}
}
#5
-2
It has been several years since the question was asked but I think it is worth noting that the BackgroundWorker class is designed precisely to achieve A, B and C requirements.
问题被问到已经有好几年了,但我认为值得注意的是,BackgroundWorker类的设计正是为了达到A,B和C的要求。
Complete sample in msdn reference page: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx
msdn参考页面中的完整示例:https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx