web-dev-qa-db-ja.com

async/await - タスク無効を返すタイミング

どのようなシナリオで使用したいですか

public async Task AsyncMethod(int num)

の代わりに

public async void AsyncMethod(int num)

私が考えることができる唯一のシナリオは、あなたがその進行を追跡することができるタスクが必要な場合です。

また、次の方法で、asyncキーワードとawaitキーワードは不要ですか。

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}
404
user981225

1)通常はTaskを返します。主な例外は、 need voidの戻り値の型(イベント用)である場合です。呼び出し側awaitがあなたのタスクを持つことを許可する理由がない場合、なぜそれを許可しないのですか?

2)asyncを返すvoidメソッドは別の面で特別です:それらは トップレベルの非同期操作 を表し、そしてタスクが例外を返すときに有効になる追加のルールを持ちます。最も簡単な方法は、違いを示すことです。

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

fの例外は常に "監視"されています。トップレベルの非同期メソッドを終了させる例外は、他の未処理の例外と同様に扱われます。 gの例外は決して見られません。ガベージコレクタがタスクをクリーンアップするとき、タスクが例外をもたらしたことがわかり、誰も例外を処理しませんでした。それが起こると、TaskScheduler.UnobservedTaskExceptionハンドラが実行されます。あなたはこれを起こさせてはいけません。あなたの例を使用すると、

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

はい、ここではasyncawaitを使用します。例外がスローされた場合でも、これらはメソッドが正しく機能することを確認します。

詳細については参照してください: http://msdn.Microsoft.com/en-us/magazine/jj991977.aspx

351
user743382

私はJérômeLabanによって書かれたasyncvoidについてのこの非常に役に立つ記事に出会いました: https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2 -async-void.html

要するに、async+voidはシステムをクラッシュさせる可能性があり、通常はUIサイドのイベントハンドラでのみ使用されるべきです。

この背景にある理由は、AsyncVoidMethodBuilderによって使用される同期コンテキストで、この例では使用されていません。アンビエント同期コンテキストがない場合、async voidメソッドの本体によって処理されない例外はすべてThreadPoolにスローされます。そのような未処理の例外がスローされる可能性のある論理的な場所は他にないと思われますが、残念なことに、ThreadPoolでの未処理の例外が.NET 2.0以降のプロセスを事実上終了するため、プロセスが終了します。 AppDomain.UnhandledExceptionイベントを使用して未処理の例外をすべて傍受することはできますが、このイベントからプロセスを回復する方法はありません。

UIイベントハンドラを書くとき、例外は非同期メソッドでは見られないのと同じように扱われるので、非同期voidメソッドはどういうわけか痛みがありません。彼らはディスパッチャーに投げられます。そのような例外から回復する可能性がありますが、ほとんどの場合でこれ以上のものはありません。ただし、UIイベントハンドラ以外では、async voidメソッドを使用するのはやや危険であり、見つけるのはそれほど簡単ではありません。

34
Davide Icardi

私はこのことから明らかなアイディアを得ました。

  1. 非同期のvoidメソッドは異なるエラー処理セマンティクスを持ちます。非同期タスクまたは非同期タスクメソッドから例外がスローされると、その例外がキャプチャされてタスクオブジェクトに配置されます。 async voidメソッドではTaskオブジェクトがないため、async voidメソッドがスローされたときにアクティブだったSynchronizationContext(SynchronizationContextはコードが実行される場所を表します)で直接発生します。始まった

Async Voidメソッドからの例外がCatchで捕捉できない

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

これらの例外は、AppDomain.UnhandledExceptionまたは同様のキャッチオールイベントをGUI/ASP.NETアプリケーションに使用することで確認できますが、通常の例外処理にこれらのイベントを使用すると、保守が困難になります(アプリケーションがクラッシュします)。

  1. 非同期のvoidメソッドは、構成方法が異なります。 TaskまたはTaskを返す非同期メソッドは、await、Task.WhenAny、Task.WhenAllなどを使用して簡単に作成できます。 voidを返す非同期メソッドは、呼び出しコードに完了したことを通知する簡単な方法を提供しません。いくつかの非同期無効メソッドを開始するのは簡単ですが、いつそれらが終了したかを判断するのは容易ではありません。非同期のvoidメソッドは、開始時と終了時にSynchronizationContextに通知しますが、カスタムのSynchronizationContextは通常のアプリケーションコードに対する複雑なソリューションです。

  2. Async Voidメソッドは、同期イベントハンドラを使用する場合に便利です。同期イベントハンドラは例外をSynchronizationContextで直接発生させるためです。これは、同期イベントハンドラの動作と似ています。

詳細については、このリンクを確認してください https://msdn.Microsoft.com/ja-jp/magazine/jj991977.aspx

23

Async voidを呼び出すことの問題点は、タスクを取り戻すことすらできないということです。関数のタスクがいつ完了したかを知る方法がありません( https://blogs.msdn.Microsoft.com/oldnewthing/20170720-を参照)。 00 /?p = 96655

非同期関数を呼び出すための3つの方法があります。

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

すべての場合において、機能は一連のタスクに変換されます。違いは、関数が返すものです。

前者の場合、関数は最終的にtを生成するタスクを返します。

後者の場合、関数は製品を持たないタスクを返しますが、それがいつ完了して完了するかを知るために待つことができます。

3番目のケースは厄介なものです。 3番目のケースは2番目のケースと似ていますが、タスクを取り戻すことすらできません。関数のタスクがいつ完了したかを知る方法はありません。

非同期の無効なケースは「発砲して忘れる」です。タスクチェーンを開始しますが、いつ終了するかは関係ありません。関数が戻ったとき、あなたが知っているのは最初のawaitまでのすべてが実行されたということだけです。最初の待ち合わせの後、あなたがアクセスできない将来のすべての不特定の時点で実行されます。

7
user8128167

例外をキャッチするように注意している限り、バックグラウンド操作を開始するためにもasync voidを使用できると思います。考えですか?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}
5
bboyle1234

私の答えは簡単ですあなたは大砲を待ってボイドメソッドエラーCS4008 'void' TestAsync eを待つことができません:\ test\TestAsync\TestAsync\Program.cs

したがって、メソッドが非同期の場合は、非同期の利点を失う可能性があるため、待っておくほうがよいでしょう。

0
Serg Sh