4

C# の async await 機能で遊んでいます。UIスレッドで使用すると、期待どおりに動作します。しかし、非 UI スレッドで使用すると、期待どおりに動作しません。以下のコードを検討してください

private void Click_Button(object sender, RoutedEventArgs e)
    {
        var bg = new BackgroundWorker();
        bg.DoWork += BgDoWork;
        bg.RunWorkerCompleted += BgOnRunWorkerCompleted;
        bg.RunWorkerAsync();
    }

    private void BgOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
    {
    }

    private async void BgDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        await Method();
    }


    private static async Task Method()
    {
        for (int i = int.MinValue; i < int.MaxValue; i++)
        {
            var http = new HttpClient();
            var tsk = await http.GetAsync("http://www.ebay.com");
        }
    }

このコードを実行すると、バックグラウンド スレッドは実行時間の長いタスクがMethod完了するまで待機しません。代わりにBgOnRunWorkerCompleted、呼び出し後に即座に実行しMethodます。どうしてこんなことに?ここで何が欠けていますか?

PS: これを行う別の方法や正しい方法には興味がありません。この場合、舞台裏で実際に何が起こっているのか知りたいですか? なぜ待っていないのですか?

4

5 に答える 5

8

そのため、BgDoWorkによってバックグラウンド スレッドで呼び出されます。BackgroundWorker

を呼び出しMethod、ループを開始して呼び出しますhttp.GetAsync

GetAsynca を返し、Task別のスレッドで作業を続行します。

が完了していないため、から戻るawaitTaskTaskMethod

同様に、await inBgDoWorkは別の値を返しますTask

そのため、が戻ってきたことを確認し、それが完了したとBackgroundWorker見なします。BgDoWork

それはその後上昇しますRunWorkerCompleted


基本的には混ぜないBackgroundWorkerasync / await

于 2012-11-27T09:19:59.940 に答える
2

基本的に、コードには 2 つの問題があります。

  1. BackgroundWorkerで動作するように更新されていませんasync。そして、メソッドの要点は、ブロックするのではなく、まだ終わっていないものをasync初めて返すということです。awaitしたがって、メソッドが ( の後にawait) 戻ると、BackgroundWorker完了したと見なされて が発生しRunWorkerCompletedます。
  2. BgDoWork()async void方法です。このような方法は「ファイア アンド フォーゲット」であり、完了するのを待つことはできません。したがって、 を理解するものでメソッドを実行する場合は、asyncそれも method に変更する必要がありますasync Task

代替手段を探していないとおっしゃいましたが、代替手段を提供すれば問題を理解するのに役立つと思います。BgDoWork()バックグラウンド スレッドで実行され、UI スレッドで実行されると仮定するとBgOnRunWorkerCompleted()、次のようなコードを使用できます。

private async void Click_Button(object sender, RoutedEventArgs e)
{
    await Task.Run((Func<Task>)BgDoWork);
    BgOnRunWorkerCompleted();
}

private void BgOnRunWorkerCompleted()
{
}

private async Task BgDoWork()
{
    await Method();
}

ここでは、 -aware の代替として機能しTask.Run()ます(バックグラウンド スレッドでメソッドを実行し、実際に完了するまで待機するために使用できる を返します)。の後、UI スレッドに戻るので、そこでが実行されます。はメソッドであり、これを使用したい唯一の状況です: イベント ハンドラー メソッドで、待機する必要はありません。asyncBackgroundWorkerTaskawaitClick_Button()BgOnRunWorkerCompleted()Click_Button()async void

于 2012-11-27T11:01:51.097 に答える
1

完了するのを待っている間、バックグラウンドスレッドが生き続けるには何らかの理由が必要だと思いますMethod()。未解決の継続があるだ​​けではスレッドを存続させるのに十分ではないため、バックグラウンド ワーカーはMethod()完了する前に終了します。

これは、コードを変更して、バックグラウンド スレッドThread.Sleepawait Method(). これはほぼ間違いなく、実際に望んでいる動作ではありませんが、スレッドが十分長くスリープ状態になれば、完了したことがわかりますMethod()

于 2012-11-27T09:11:35.943 に答える
1

待機するものを何も返さないため、イベント ハンドラーを待機することはできません。asyncキーワードのドキュメントから:

void 戻り値の型は、主に、void 戻り値の型が必要なイベント ハンドラーを定義するために使用されます。void を返す非同期メソッドの呼び出し元は、それを待つことができず、メソッドがスローする例外をキャッチできません。

BgDoWork イベント ハンドラーに async キーワードを追加することで、ハンドラーを非同期で実行し、最初の生成操作が検出されるとすぐに戻るように .NET に指示します。この場合、これは http.GetAsync への最初の呼び出しの後に発生します。

于 2012-11-28T13:10:38.807 に答える
1

以下は、 DoWorkがどのように発生し、処理されるかです。( Reflectorツールを使用して取得したコード)。

private void WorkerThreadStart(object argument)
{
    object result = null;
    Exception error = null;
    bool cancelled = false;
    try
    {
        DoWorkEventArgs e = new DoWorkEventArgs(argument);
        this.OnDoWork(e);
        if (e.Cancel)
        {
            cancelled = true;
        }
        else
        {
            result = e.Result;
        }
    }
    catch (Exception exception2)
    {
        error = exception2;
    }
    RunWorkerCompletedEventArgs arg = new RunWorkerCompletedEventArgs(result, error, cancelled);
    this.asyncOperation.PostOperationCompleted(this.operationCompleted, arg);
}


protected virtual void OnDoWork(DoWorkEventArgs e)
{
    DoWorkEventHandler handler = (DoWorkEventHandler) base.Events[doWorkKey];
    if (handler != null)
    {
        handler(this, e);
    }
}

async メソッドを待機するための特別な処理はありません。(async/await キーワードを使用)。

非同期操作を待機させるには、次の変更が必要です。

async private void WorkerThreadStart(object argument)

    await this.OnDoWork(e);


async protected virtual void OnDoWork(DoWorkEventArgs e)

    await handler(this, e);

ただし、BackgroundWorkerは .net 2.0 コンストラクトであり、async/awaitは .net 4.5 です。これらのいずれかが他の構成を使用している場合、完全な円になります。

于 2012-11-27T09:33:05.007 に答える