web-dev-qa-db-ja.com

最適な非同期whileメソッド

データベースと繰り返し対話して初期化しようとする非同期コードを記述する必要があります。多くの場合、最初の試行は失敗するため、再試行する必要があります。

昔は、次のようなパターンを使用していました。

void WaitForItToWork()
{
    bool succeeded = false;
    while (!succeeded)
    {
        // do work
        succeeded = outcome; // if it worked, mark as succeeded, else retry
        Threading.Thread.Sleep(1000); // arbitrary sleep
    }
}

非同期パターンに関して最近.NETに多くの変更が加えられたことに気付いたので、私の質問は、これが実際に使用するのに最適な方法であるか、またはasyncのものを探索する際に価値があるのか​​、そしてどのように実装するのかこのパターンはasync

更新

明確にするために、サービスのコンストラクターでスポーンされるため、スポーンするメソッドがそれが完了するのを待つ必要がないように、このワークを非同期でスポーンしたいので、コンストラクターは即座に返す必要があります。

22
Chris

このフラグメントを次のようにリファクタリングできます。

_async Task<bool> WaitForItToWork()
{
    bool succeeded = false;
    while (!succeeded)
    {
        // do work
        succeeded = outcome; // if it worked, make as succeeded, else retry
        await Task.Delay(1000); // arbitrary delay
    }
    return succeeded;
}
_

明らかに、それがもたらす唯一の利点は、スレッドプールをより効率的に使用することです。これは、遅延を発生させるために常にスレッド全体をとるわけではないためです。

outcomeの取得方法によっては、_async/await_を使用してこのジョブを実行するより効率的な方法が存在する場合があります。多くの場合、GetOutcomeAsync()のようなものがあり、これによりWebサービス、データベース、またはソケットが自然な方法で非同期に呼び出されるため、var outcome = await GetOutcomeAsync()を実行します。

WaitForItToWorkはコンパイラーによって部分に分割され、await行からの部分は非同期で継続されることを考慮することが重要です。 こちら おそらく内部でどのように行われるかについての最も良い説明です。問題は、通常、コードのある時点で、非同期タスクの結果を同期する必要があることです。例えば。:

_private void Form1_Load(object sender, EventArgs e)
{
    Task<bool> task = WaitForItToWork();
    task.ContinueWith(_ => {
        MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false
    }, TaskScheduler.FromCurrentSynchronizationContext());
}
_

あなたは単にこれを行うことができたでしょう:

_private async void Form1_Load(object sender, EventArgs e)
{
    bool result = await WaitForItToWork();
    MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false
}
_

ただし、これにより_Form1_Load_も非同期メソッドになります。

[UPDATE]

以下は、_async/await_がこの場合に実際に何をするかを説明する私の試みです。同じロジックの2つのバージョンを作成しました、WaitForItToWorkAsync(_async/await_を使用)およびWaitForItToWorkAsyncTap(_async/await_なしで TAPパターン を使用)。最初のバージョンとは異なり、最初のバージョンは非常に簡単です。したがって、_async/await_は主にコンパイラの構文上の砂糖ですが、非同期コードの記述と理解がはるかに容易になります。

_// fake outcome() method for testing
bool outcome() { return new Random().Next(0, 99) > 50; }

// with async/await
async Task<bool> WaitForItToWorkAsync()
{
    var succeeded = false;
    while (!succeeded)
    {
        succeeded = outcome(); // if it worked, make as succeeded, else retry
        await Task.Delay(1000);
    }
    return succeeded;
}

// without async/await
Task<bool> WaitForItToWorkAsyncTap()
{
    var context = TaskScheduler.FromCurrentSynchronizationContext();
    var tcs = new TaskCompletionSource<bool>();
    var succeeded = false;
    Action closure = null;

    closure = delegate
    {
        succeeded = outcome(); // if it worked, make as succeeded, else retry
        Task.Delay(1000).ContinueWith(delegate
        {
            if (succeeded)
                tcs.SetResult(succeeded);
            else
                closure();
        }, context);
    };

    // start the task logic synchronously
    // it could end synchronously too! (e.g, if we used 'Task.Delay(0)')
    closure();

    return tcs.Task;
}

// start both tasks and handle the completion of each asynchronously
private void StartWaitForItToWork()
{
    WaitForItToWorkAsync().ContinueWith((t) =>
    {
        MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString());
    }, TaskScheduler.FromCurrentSynchronizationContext());

    WaitForItToWorkAsyncTap().ContinueWith((t) =>
    {
        MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString());
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

// await for each tasks (StartWaitForItToWorkAsync itself is async)
private async Task StartWaitForItToWorkAsync()
{
    bool result = await WaitForItToWorkAsync();
    MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString());

    result = await WaitForItToWorkAsyncTap();
    MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString());
}
_

スレッドに関するいくつかの単語。ここで明示的に作成された追加のスレッドはありません。内部的には、Task.Delay()実装はプールスレッドを使用する可能性があります(それらは Timer Queues を使用していると思われます)が、この特定の例(WinFormsアプリ)では、awaitの後の継続同じUIスレッドで発生します。他の実行環境(コンソールアプリなど)では、別のスレッドで継続する場合があります。 IMO、 この記事 by Stephen Clearyは、_async/await_スレッドの概念を理解するために必読です。

31
noseratio

タスクが非同期の場合、次のように試すことができます:

    async Task WaitForItToWork()
    {
        await Task.Run(() =>
        {
            bool succeeded = false;
            while (!succeeded)
            {
                // do work
                succeeded = outcome; // if it worked, make as succeeded, else retry
                System.Threading.Thread.Sleep(1000); // arbitrary sleep
            }
        });
    }

http://msdn.Microsoft.com/en-us/library/hh195051.aspx を参照してください。

2

別のソリューションを提供するだけ

public static void WaitForCondition(Func<bool> predict)
    {
        Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ =>
        {
            var result = predict();
            // the condition result is false, and we need to wait again.
            if (result == false)
            {
                WaitForCondition(predict);
            }
        });
    }
1
user2986287

実際にはWaitItForWorkメソッドは必要ありません。データベースの初期化タスクを待つだけです。

async Task Run()
{
    await InitializeDatabase();
    // Do what you need after database is initialized
}

async Task InitializeDatabase()
{
    // Perform database initialization here
}

WaitForItToWorkを呼び出すコードが複数ある場合は、データベースの初期化をTaskにラップして、すべてのワーカーで待機する必要があります。次に例を示します。

readonly Task _initializeDatabaseTask = InitializeDatabase();

async Task Worker1()
{
    await _initializeDatabaseTask;
    // Do what you need after database is initialized
}

async Task Worker2()
{
    await _initializeDatabaseTask;
    // Do what you need after database is initialized
}

static async Task InitializeDatabase()
{
    // Initialize your database here
}
0