Kic*_*aha 44 c# wpf asynchronous
我有一个WPF GUI,我想按下一个按钮来启动一个长任务,而不会在任务期间冻结窗口.当任务正在运行时,我想获得有关进度的报告,我想在我选择的任何时候添加另一个按钮来停止任务.
我无法想出使用async/await/task的正确方法.我不能包括我尝试的所有东西,但这就是我现在拥有的东西.
一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
还有一个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;
}
}
Run Code Online (Sandbox Code Playgroud)
这会运行,但是一旦工作方法启动,WPF功能窗口仍会被阻止.
我需要知道如何安排async/await/task声明来允许
A)不阻止gui窗口
的worker方法B)让worker方法更新gui窗口
C)允许gui窗口停止中断并停止worker方法
任何帮助或指针都非常感谢.
Biz*_*han 89
长话短说:
private async void ButtonClick(object sender, RoutedEventArgs e)
{
// modify UI object in UI thread
txt.Text = "started";
// run a method in another thread
await Task.Run(()=> HeavyMethod(txt));
// <<method execution is finished here>>
// modify UI object in UI thread
txt.Text = "done";
}
// This is a thread-safe method. You can run it in any thread
internal void HeavyMethod(TextBox textBox)
{
while (stillWorking)
{
textBox.Dispatcher.Invoke(() =>
{
// UI operation goes inside of Invoke
textBox.Text += ".";
});
// CPU-bound or I/O-bound operation goes outside of Invoke
System.Threading.Thread.Sleep(51);
}
}
Run Code Online (Sandbox Code Playgroud)
你只能async
在一个await
方法中.
await
async
在线程池中对a 进行排队(即它使用线程池中的现有线程或在线程池中创建新线程来运行任务)
执行等待await
任务完成并返回其结果,而不会因为awaitable
关键字的魔法能力而阻塞主线程:
在魔术的Task
关键字是,它不创建另一个线程.它只允许编译器放弃并取消对该方法的控制.
值得一提的是,如果Task<T>
是类型,ValueTask<T>
则async
返回值Task
.if await
是类型Task.Run
然后Task
不返回任何东西(或返回await
)
所以
你的主线程调用async-await
方法(awaited Task
)就像普通方法一样,到目前为止还没有线程......现在你可以在这里运行一个任务async
:
Result:
started....................done
Run Code Online (Sandbox Code Playgroud)
或者干脆
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
Task task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
//wait for it to end without blocking the main thread
await task;
}
Run Code Online (Sandbox Code Playgroud)
或者如果async
返回值为typeasync
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
await Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
}
Run Code Online (Sandbox Code Playgroud)
或者干脆
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
Task<string> task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
string returnValue = await task;
}
Run Code Online (Sandbox Code Playgroud)
任务(或Task
)内部的方法异步运行,如下所示:
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
string returnValue = await Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
//or in cases where you already have a "Task returning" method:
// var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}
Run Code Online (Sandbox Code Playgroud)
Task
是较新的(.NetFX4.5)和更简单的版本 async
async
是不是 Task
await
即使在带有await
关键字的方法中调用它,也会阻塞主线程.
ExecuteLongProcedure
防止任务因string
关键字而阻塞主线程.
//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 ExecuteLongProcedure(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
// ExecuteLongProcedure is called in an async method
System.Threading.Thread.Sleep(51);
}
gui.UpdateWindow("Done and Done");
}
Run Code Online (Sandbox Code Playgroud)
如果必须异步访问GUI(内部ExecuteLongProcedure
方法),请调用涉及访问GUI字段的任何操作:
private async Task<int> GetOneAsync()
{
return 1; // return type is a simple int
// while the method signature indicates a Task<int>
}
Run Code Online (Sandbox Code Playgroud)
但是,如果由于属性从ViewModel 更改回调而启动任务,则无需使用,private async ReturnType Method() { ... }
因为回调实际上是从UI线程执行的.
在非UI线程上访问集合
WPF使您可以访问和修改除创建集合之外的线程上的数据集合.这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据.通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互.
由INotifyPropertyChanged触发的值更改会自动编组回调度程序.
请记住,ReturnType
方法本身在主线程上运行.所以这是有效的:
int number = await Task.Run(() => 1); // right hand of await is a Task<int>
// while the left hand is an int
Run Code Online (Sandbox Code Playgroud)
您还可以阅读一个简单的异步文件编写器,以了解应该并发的位置.
调查并发命名空间
最后阅读这本电子书:Patterns_of_Parallel_Programming_CSharp
你的使用TaskCompletionSource<T>
是不正确的.TaskCompletionSource<T>
是一种为异步操作创建TAP兼容包装器的方法.在您的ExecuteLongProcedureAsync
方法中,示例代码都是CPU绑定的(即,本质上是同步的,而不是异步的).
因此,编写ExecuteLongProcedure
同步方法更为自然.这也是使用标准的类型标准的行为,尤其是一个好主意,使用IProgress<T>
了最新进展和CancellationToken
注销:
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");
}
Run Code Online (Sandbox Code Playgroud)
现在,您有一个使用适当约定的更可重用的类型(没有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;
}
}
Run Code Online (Sandbox Code Playgroud)
这是 Bijan 此处最受欢迎的答案的简化版本。我简化了 Bijan 的答案,以帮助我使用 Stack Overflow 提供的漂亮格式来思考问题。
通过仔细阅读和编辑Bijan的帖子,我终于明白了:如何等待异步方法完成?
在我的情况下,为另一篇文章选择的答案最终让我解决了我的问题:
“避免async void
。让你的方法返回Task
而不是void
。然后你就可以await
了。”
我的 Bijan(优秀)答案的简化版本如下:
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));
}
Run Code Online (Sandbox Code Playgroud)
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");
}
Run Code Online (Sandbox Code Playgroud)
3) 从 gui 调用涉及属性的操作:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
Run Code Online (Sandbox Code Playgroud)
或者,
void UpdateWindow(string text)
{
//simply
txt.Text += text;
}
Run Code Online (Sandbox Code Playgroud)
结束评论)在大多数情况下,您有两种方法。
第一个方法 ( Button_Click_3
) 调用第二个方法并具有async
告诉编译器为该方法启用线程的修饰符。
Thread.Sleep
在一个async
方法中阻塞了主线程。但等待任务不会。await
语句上的当前线程(第二个线程)上停止,直到任务完成。await
在async
方法之外使用第二个方法 ( ExecuteLongProcedureAsync
) 被包装在一个任务中并返回一个通用Task<original return type>
对象,可以通过await
在它之前添加来指示异步处理该对象。
列罗提出了一个重要的问题。当您将元素绑定到 ViewModel 属性时,在 UI 线程中执行属性更改回调。所以没有必要使用Dispatcher.Invoke
. INotifyPropertyChanged 触发的值更改会自动编组回调度程序。
这是一个使用async/await
,IProgress<T>
和的示例CancellationTokenSource
。这些是您应该使用的现代 C# 和 .Net Framework 语言功能。其他解决方案让我的眼睛流血了一点。
<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>
Run Code Online (Sandbox Code Playgroud)
/// <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();
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
51017 次 |
最近记录: |