サーチ…


備考

これらの例を実行するには、次のように呼び出します。

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と同じです)、 useruser2両方に同じデータが入力されます。

このため、コンテキストがもはや使用されていないライブラリコードで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グラウンドスレッドで時間のかかる操作を実行する方法の簡単な例については、以下を参照してください。

必要がある:

  1. 時間のかかる作業を行うワーカー・メソッドを定義し、 BackgroundWorker DoWorkイベントのイベント・ハンドラからコールします。
  2. 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.");
}

TimeIntensiveMethodThreadはパラメータとして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.
    });


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow