web-dev-qa-db-ja.com

asyncを使用して適切に破棄して待機する方法

コードをThreadからTaskに置き換えようとしています。スリープ/遅延は、長時間実行されているアクティビティを表しているだけです。

static void Main(string[] args)
{
    ThreadDoWork();
    TaskDoWork();
}
public static void ThreadDoWork()
{
    using (var dispose = new ThreadDispose())
    {
        dispose.RunAsync();
    }
}
public static async void TaskDoWork()
{
    using (var dispose = new TaskDispose())
    {
        await dispose.RunAsync();
    }
}
public class ThreadDispose : IDisposable
{
    public void RunAsync ()
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            Thread.Sleep(3000);
        });
    }
    void IDisposable.Dispose()
    {
        File.AppendAllText("D:\\test.txt", "thread disposing");
    }
}
public class TaskDispose : IDisposable
{
    public async Task RunAsync()
    {
        await Task.Delay(3000);
    }
    void IDisposable.Dispose()
    {
        File.AppendAllText("D:\\test.txt", "task disposing");
    }
}

test.txtで3秒後の結果は

スレッド処理

TaskDispose::Disposeが常にThreadDisposeと同じように実行されるようにするには、何を変更する必要がありますか?

15
Yuliam Chandra

コードの各部分を分離しましょう:

public static void ThreadDoWork()
{
    using (var dispose = new ThreadDispose())
    { 
        dispose.RunAsync();
    }
}

public void RunAsync()
{
    ThreadPool.QueueUserWorkItem(state =>
    {
        Thread.Sleep(3000);
    });
}

この最初のコードで行うことは、スレッドプールスレッドでのキュー作業です。このコードはusingスコープ内で実行されており、別のスレッドで非同期に実行されるため、すぐに破棄されます。そのため、テキストファイル内に破棄メッセージが表示されます。

public static async void TaskDoWork()
{
   using (var dispose = new TaskDispose())
   {
       await dispose.RunAsync();
   }
}

public class TaskDispose : IDisposable
{
   public async Task RunAsync()
   {
       await Task.Delay(3000);
   }
}

メソッド内でawaitすると、実際に言うことは次のようになります。 "このコードを実行します。本質的に非同期であるため、呼び出し元のメソッドに制御を戻します。 、非同期操作が完了したら、折り返し電話してください」

コードがawaitキーワードにヒットし、制御をMainメソッドに戻します。 Main内では、非同期メソッドが実行される最後のコードであるため、アプリケーションが終了し、Disposeメソッドが実行される機会が与えられません。

破棄する場合は、戻り値の型をvoidからTaskに変更し、明示的にWaitにする必要があります。

public static async Task TaskDoWork()
{
    using (var dispose = new TaskDispose())
    {
       await dispose.RunAsync();
    }
}

そして今:

static void Main(string[] args)
{
    ThreadDoWork();
    TaskDoWork().Wait();
}

サイドノート:

従う必要のあるガイドラインがいくつかあります。

  1. async voidは、イベントハンドラーとの互換性のためのものであり、その範囲外で使用する必要がある場合はめったにありません。代わりに、async Taskを使用してください。

  2. TAP(タスク非同期パターン)を使用して非同期操作を実行するメソッドは、Async接尾辞で終了する必要があります。 TaskDoWorkTaskDoWorkAsyncである必要があります

  3. WaitTaskを使用すると、デッドロックが発生する可能性があります。この特定のケースでは、コンソールアプリケーションにSynchronizationContextがなく、スレッドプールを使用しているため、そうではありません。推奨されるアプローチは、「ずっと非同期」にしてawaitを使用することです。

async-await tag wiki の中にはすばらしい読み物がありますので、ぜひチェックしてください。

22
Yuval Itzchakov