C#で、重い処理をしているときなどに、マルチスレッドで、
別タスクからUIをさわりたいときがあるのですが、エラーが出てしまいます。
そんなときは、Task型の関数を、awaitで実行すればいいのです。
例
taskUI.cs
using System.Threading.Tasks;
// 省略
async void Main()
{
var heavyResult = await runTask(); // 1.
label1.Text = heavyResult;
}
async Task<string> runTask()
{
await Task.Delay(1000); // 2.
return "hogehoge"; // 3.
}
番号はコード内のコメントの番号と対応しています。
- 非同期で
runTask
関数を実行 - 別Taskでの動作の確認のために、1秒間止める。この間もUIは動作する。
- 1秒間たった後、文字列
hogehoge
を返す。 - 戻り値をlabelに書きだす。
- Mainメソッドに、asyncをつけています
また、タスクを別のメソッドにしなくてもできるようです。
taskUI2.cs
using System.Threading.Tasks;
// 省略
async void Main()
{
var heavyResult = await Task.Run(async () =>
{
await Task.Delay(1000);
return "fugafuga";
}
label1.Text = heavyResult;
}
何回もLabelを書き替えたい
なんか重い処理が、「何%進みました」みたいな処理をやるときは、
複数回Labelを書き替える必要があります。
その時は、Taskをつなげて行うのではなく、1%ごとに分けて行うとできます。
async void Main()
{
for (var i = 0; i < 100; i++)
{
label1.Text = await progress(i);
}
}
async Task<string> progress(int percent)
{
await Task.Delay(100);
return $"{percent}% 完了しました";
}
追記:Reactive Extensions
Reactive Extensionsを使うと、複数のタスクに分けず、JavaScriptのコールバックみたいなことができます。
デフォルトでは、Reactive Extensionsが入っていないので、
NuGetで「Rx-main」をインストールしてください。
詳細: https://msdn.microsoft.com/en-us/data/gg577610.aspx#dotnet
using System.Reactive.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
void Main()
{
Observable.Create<string>(async o =>
{
for (var i = 0; i < 100; i++)
{
await Task.Delay(100);
o.OnNext($"{i}% 完了しました。");
}
o.OnNext("最後の処理をしています...");
await Task.Delay(1000);
o.OnNext("完了");
return Disposable.Empty;
}).Subscribe(x =>
{
label1.Text = x;
});
}
この方法だと、毎回違う処理をする場合でも、一つのObservableで処理ができます。
NetSeedさん、chocolamintさん、ありがとうございます。