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;
}
}
ワーカーメソッドのクラス:
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;
}
}
これは実行されますが、ワーカーメソッドが開始されると、WPF関数ウィンドウは引き続きブロックされます。
Async/await/task宣言をどのように調整して許可するかを知る必要があります
A)GUIウィンドウをブロックしないワーカーメソッド
B)ワーカーメソッドにGUIウィンドウを更新させます
C)GUIウィンドウで割り込みを停止し、ワーカーメソッドを停止できるようにする
どんな助けやポインタも大歓迎です。
長い話:
private async void ButtonClick(object sender, RoutedEventArgs e)
{
txt.Text = "started";// done in UI thread
// wait for the task to finish, but don't block the UI thread
await Task.Run(()=> HeavyMethod(txt));
// The task is now completed.
txt.Text = "done";// done in UI thread
}
// Running the Task causes this method to be executed in Thread Pool
internal void HeavyMethod(TextBox /*or any Control or Window*/ txt)
{
while (stillWorking)
{
txt/*or a control or a window*/.Dispatcher.Invoke(() =>
{
// UI operations go inside of Invoke
txt.Text += ".";
});
// CPU-bound or I/O-bound operations go outside of Invoke
System.Threading.Thread.Sleep(51);
}
}
Result:
txt.Text == "started....................done"
await
メソッドで使用できるのはasync
のみです。
await
のみawaitable
オブジェクト(つまり、Task
またはTask<T>
)を使用できます
Task.Run
通常スレッドプールのTask
をキューに入れます(つまり、スレッドプールの既存のスレッドを使用するか、スレッドプールに新しいスレッドを作成してタスクを実行します。これはすべて当てはまります。非同期操作が pure 操作でない場合、スレッドはなく、OSとデバイスドライバーによって処理される純粋な非同期操作だけです)
await
キーワードの魔法の能力のために、メインスレッドをブロックせずに、タスクは完了するまでasync
で待機し、結果を返します。
async
キーワードのmagicは、not別のスレッドを作成することです。コンパイラーがあきらめるおよび取り戻すだけでそのメソッドを制御できるようにします。 (メソッドとasync
キーワードを、Task
で囲まれたメソッドと混同しないでください)
そう
あなたのメインスレッドは、通常のメソッドのようにasync
メソッド(MyButton_Click
)を呼び出し、これまでスレッド化を行いません...これで、MyButton_Click
内でタスクを実行できます:
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;
}
または単に
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
await Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
}
またはExecuteLongProcedure
がstring
型の戻り値を持っている場合
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
Task<string> task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3));
string returnValue = await 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();
}
タスク内のメソッド(またはExecuteLongProcedure
)は非同期的にを実行し、次のようになります。
//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 with await keyword
System.Threading.Thread.Sleep(51);
}
gui.UpdateWindow("Done and Done");
}
task
がTask<T>
型の場合、await task
ステートメントによって返される値は、T
型の値です。 task
がTask
型の場合、await task
は何も返しません(またはvoid
を返します)。この時点で、コンパイラにawait
にタスクを終了するよう指示するか、次の行に進むことができます。
したがって、async
メソッドが何も返さない場合は、async void MyMethod()
またはasync Task MyMethod()
を記述できます。また、async
メソッドが何か(整数など)を返している場合は、async Task<int> MyMethod
を記述できます。この場合、コードは次のようになります。
private async Task<int> MyMethod()
{
int number = await Task.Run(todo);
return number;
}
結果を待ちたくない場合の場合、非同期メソッドの戻り値のタイプとしてTask
はおそらく必要ないため、これは明らかです。ただし、結果を待ちたい場合は、awaitasyncメソッドの結果そのメソッド内で行ったのと同じ方法である必要があります。例えばvar asyncResult = await MyMethod()
まだ混乱していますか? [〜#〜] msdn [〜#〜] で非同期戻り型を読み取ります。
Task.Run
は、Task.Factory.StartNew
のより新しい(.NetFX4.5)およびシンプルなバージョンです
await
はnotTask.Wait()
です
Sleep
などのCPUバインドまたはIOバインド操作は、async
キーワードを使用したメソッドで呼び出された場合でも、メインスレッドをblockします。 (再び、async
メソッドをTask
内のメソッドと混同しないでください。非同期メソッド自体がタスクとして実行される場合、これは明らかに正しくありません:await MyAsyncMethod
)
await
防止は、コンパイラがasync
メソッドに対する制御を放棄するため、メインスレッドをブロックしません。
private async void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);//blocks
await Task.Run(() => Thread.Sleep(1000));//does not block
}
GUIに非同期でアクセスする必要がある場合(ExecuteLongProcedure
メソッド内)、invokeスレッドセーフでないオブジェクトへの変更を伴う操作。たとえば、WPF GUIオブジェクトは、GUIスレッドに関連付けられているDispatcher
オブジェクトを使用して呼び出す必要があります。
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
ただし、ViewModelからのproperty changed callbackの結果としてタスクが開始された場合、コールバックは実際にUIスレッドから実行されるため、Dispatcher.Invoke
を使用する必要はありません。
非UIスレッドのコレクションへのアクセス
WPFを使用すると、コレクションを作成したスレッド以外のスレッドでデータコレクションにアクセスして変更できます。これにより、バックグラウンドスレッドを使用して、データベースなどの外部ソースからデータを受信し、UIスレッドでデータを表示できます。別のスレッドを使用してコレクションを変更することにより、ユーザーインターフェイスはユーザーの操作に応答し続けます。
INotifyPropertyChangedによって発生した値の変更は、自動的にディスパッチャーにマーシャリングされます。
async
メソッド自体はメインスレッドで実行されることに注意してください。これは有効です:
private async void MyButton_Click(object sender, RoutedEventArgs e)
{
txt.Text = "starting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure1());
txt.Text = "waiting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure2());
txt.Text = "finished"; // UI Thread
}
戻り値の型がTask
またはTask<T>
のメソッド名にAsync
を後置するだけです。例えば:
Task WriteToFileAsync(string fileName)
{
return Task.Run(()=>WriteToFile(fileName));
}
async void DoJob()
{
await WriteToFileAsync("a.txt");
}
void Main()
{
DoJob();
}
Task.Run()
に渡されるメソッドには、Async
接尾辞を使用しないでください。
個人的には、Async
接尾辞をnotがTask
またはTask<T>
を返すメソッドに使用すべきではないと思います。ただし、ほとんどの人は、async
メソッドでこのプレフィックスを使用します。
いいえ。async
、そのcontext、および継続について学ぶべきことはまだたくさんあります。
必ずしも。 この回答 を読んで、async
の正体について詳しく知ることができます。
Stephen Cleary はasync-await
を完全に説明しました。また、彼は 他のブログ投稿 スレッドが関与していない場合について説明しています。
非同期、並列、並行の違いを必ず確認してください。
また、 単純な非同期ファイルライターを読むこともできます どこで並行すべきかを知るため。
調査 同時ネームスペース
最終的に、この電子書籍をお読みください: 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");
}
これで、適切な規則を使用する、より再利用可能なタイプ(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;
}
}
これは、Bijanによる最も一般的な回答の簡略版です。私は、Bijanの答えを単純化して、Stack Overflowが提供するNice形式を使用して問題を考えるのを助けました。
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));
}
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)GUIのプロパティを含む操作を呼び出します。
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
または、
void UpdateWindow(string text)
{
//simply
txt.Text += text;
}
最後のコメント)ほとんどの場合、2つの方法があります。
最初のメソッド(Button_Click_3
)は2番目のメソッドを呼び出し、そのメソッドのスレッド化を有効にするようコンパイラーに指示するasync
修飾子を持っています。
async
メソッドのThread.Sleep
は、メインスレッドをブロックします。しかし、タスクを待つことはしません。await
ステートメントの現在のスレッド(2番目のスレッド)で実行が停止します。await
メソッドの外でasync
を使用することはできません2番目のメソッド(ExecuteLongProcedureAsync
)はタスク内にラップされ、await
を前に追加することで非同期的に処理するように指示できる汎用Task<original return type>
オブジェクトを返します。
Lieroは重要な問題を提起しました。要素をViewModelプロパティにバインドすると、プロパティが変更されたコールバックがUIスレッドで実行されます。したがって、Dispatcher.Invoke
を使用する必要はありません。 INotifyPropertyChangedによって発生した値の変更は、自動的にディスパッチャーにマーシャリングされます。
async/await
、IProgress<T>
およびCancellationTokenSource
を使用した例を次に示します。これらは、あなたがshouldを使用している最新の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>
/// <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();
}
}