C# Language
非同期/待機、バックグラウンドワーカー、タスク、スレッドの例
サーチ…
備考
これらの例を実行するには、次のように呼び出します。
static void Main()
{
new Program().ProcessDataAsync();
Console.ReadLine();
}
ASP.NET構成待機中
ASP.NETが要求を処理すると、スレッドプールからスレッドが割り当てられ、 要求コンテキストが作成されます。リクエストコンテキストには、静的なHttpContext.Current
プロパティでアクセスできる現在のリクエストに関する情報が含まれています。要求の要求コンテキストは、要求を処理するスレッドに割り当てられます。
所与の要求コンテキストは、一度に1つのスレッドでしかアクティブではない 。
実行がawait
と、非同期メソッドが実行されている間に要求を処理するスレッドがスレッドプールに返され、別のスレッドが使用するために要求コンテキストが解放されます。
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
var products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
// Execution continues using the original request context.
return View(products);
}
タスクが完了すると、スレッドプールは別のスレッドを割り当てて要求の実行を続行します。要求コンテキストは、このスレッドに割り当てられます。これは元のスレッドかもしれません。
ブロッキング
async
メソッド呼び出しの結果が同期的に待機すると、デッドロックが発生する可能性があります。たとえば、次のコードでは、 IndexSync()
が呼び出されたときにデッドロックが発生しIndexSync()
。
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
これは、デフォルトではdb.Products.ToListAsync()
しているタスクdb.Products.ToListAsync()
この場合はdb.Products.ToListAsync()
がコンテキスト(ASP.NETの場合は要求コンテキスト)を取得し、完了後に使用しようとするためです。
コールスタック全体が非同期である場合は、 await
になると元のスレッドが解放され、要求コンテキストが解放されるため、問題はありません。
Task.Result
またはTask.Wait()
(または他のブロッキングメソッド)を使用して同期的にブロックすると、元のスレッドは依然としてアクティブであり、要求コンテキストを保持します。待機しているメソッドは依然として非同期で動作し、コールバックが実行しようとすると、つまり待機しているタスクが戻ったら、要求コンテキストを取得しようとします。
したがって、非同期操作が完了するのを待機している要求コンテキストを持つブロックスレッドでは、非同期操作が完了するために要求コンテキストを取得しようとしているため、デッドロックが発生します。
ConfigureAwait
デフォルトでは、待機しているタスクを呼び出すと、現在のコンテキストが取得され、完了したらコンテキストで実行を再開しようとします。
ConfigureAwait(false)
を使用することで、これを防ぐことができ、デッドロックを回避できます。
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);
// Execution resumes on a "random" thread from the pool without the original request context
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
これは、非同期コードをブロックする必要があるときにデッドロックを回避することができますが、これは継続中のコンテキストを失うことになります(呼び出し待ちのコード)。
ASP.NETでは、これは、あなたのコードがawait someTask.ConfigureAwait(false);
をawait someTask.ConfigureAwait(false);
続く場合、そのことを意味しますawait someTask.ConfigureAwait(false);
例えば、 HttpContext.Current.User
などのコンテキストから情報にアクセスしようとすると、情報が失われてしまいます。この場合、 HttpContext.Current
はnullです。例えば:
public async Task<ActionResult> Index()
{
// Contains information about the user sending the request
var user = System.Web.HttpContext.Current.User;
using (var client = new HttpClient())
{
await client.GetAsync("http://google.com").ConfigureAwait(false);
}
// Null Reference Exception, Current is null
var user2 = System.Web.HttpContext.Current.User;
return View();
}
ConfigureAwait(true)
を使用すると(ConfigureAwaitをまったく持たないuser
と同じです)、 user
とuser2
両方に同じデータが入力されます。
このため、コンテキストがもはや使用されていないライブラリコードでConfigureAwait(false)
を使用することをお勧めします。
非同期/待機
async / awaitを使用してバックグラウンドプロセスで時間のかかるものを実行する方法の簡単な例については、以下を参照してください。
ただし、時間のかかるメソッドの結果を後で処理する必要がある場合は、実行を待つことでこれを行うことができます。
public async Task ProcessDataAsync()
{
// Start the time intensive method
Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
// Wait for TimeintensiveMethod to complete and get its result
int x = await task;
Console.WriteLine("Count: " + x);
}
private async Task<int> TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = await reader.ReadToEndAsync();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
BackgroundWorker
BackgroundWorker
オブジェクトを使用してBackgroundWorker
グラウンドスレッドで時間のかかる操作を実行する方法の簡単な例については、以下を参照してください。
必要がある:
- 時間のかかる作業を行うワーカー・メソッドを定義し、
BackgroundWorker
DoWork
イベントのイベント・ハンドラからコールします。 -
RunWorkerAsync
実行を開始しRunWorkerAsync
。添付ワーカー法で必要とされる任意の引数DoWork
経由で渡すことができるDoWorkEventArgs
へのパラメータRunWorkerAsync
。
DoWork
イベントに加えて、 BackgroundWorker
クラスは、ユーザーインターフェイスと対話するために使用される2つのイベントも定義します。これらはオプションです。
-
RunWorkerCompleted
際にイベントがトリガされDoWork
ハンドラが完了しました。 -
ProgressChanged
イベントは、ReportProgress
メソッドが呼び出されたときにReportProgress
れます。
public void ProcessDataAsync()
{
// Start the time intensive method
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += BwDoWork;
bw.RunWorkerCompleted += BwRunWorkerCompleted;
bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// we can access possible return values of our Method via the Parameter e
Console.WriteLine("Count: " + e.Result);
}
// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
e.Result = TimeintensiveMethod(e.Argument);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
仕事
バックグラウンドプロセスで時間を要するTask
を行うためにTask
を使用する方法の簡単な例については、以下を参照してください。
Task.Run()
呼び出しで、時間のかかるメソッドをラップするだけです。
public void ProcessDataAsync()
{
// Start the time intensive method
Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
Console.WriteLine("Count: " + t.Result);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
糸
Thread
を使用してバックグラウンドプロセスで時間のかかる作業を行う方法の簡単な例については、以下を参照してください。
public async void ProcessDataAsync()
{
// Start the time intensive method
Thread t = new Thread(TimeintensiveMethod);
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
private void TimeintensiveMethod()
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
{
string v = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
v.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
}
TimeIntensiveMethod
、 Thread
はパラメータとしてvoidメソッドを期待しているので、 TimeIntensiveMethod
から値を返すことはできません。
Thread
から戻り値を取得するには、イベントまたは次のいずれかを使用します。
int ret;
Thread t= new Thread(() =>
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something to demonstrate the coolness of await-async
ret = new Random().Next(100);
});
t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);
タスク "実行して忘れる"拡張機能
場合によっては(例えばロギング)、タスクを実行して結果を待たずに済むかもしれません。次の拡張機能では、タスクを実行して残りのコードの実行を継続できます。
public static class TaskExtensions
{
public static async void RunAndForget(
this Task task, Action<Exception> onException = null)
{
try
{
await task;
}
catch (Exception ex)
{
onException?.Invoke(ex);
}
}
}
結果は、拡張メソッド内でのみ待たれます。 async
/ await
が使用されるので、例外を捕捉し、それを処理するためのオプションのメソッドを呼び出すことができます。
拡張機能の使い方の例:
var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
e =>
{
// Something went wrong, handle it.
});