it-roy-ru.com

Лучший асинхронный метод

Мне нужно написать некоторый асинхронный код, который по существу пытается многократно общаться и инициализировать базу данных. Довольно часто первая попытка заканчивается неудачей, поэтому необходимо повторить попытку.

В старые времена я бы использовал шаблон, похожий на:

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?

Обновление

Просто чтобы прояснить, я хочу порождать эту работу асинхронно, чтобы метод, который ее порождает, не должен был ждать его завершения, поскольку он будет порожден в конструкторе службы, поэтому конструктор должен немедленно вернуться.

19
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(), которое делает асинхронный вызов веб-службы, базы данных или сокета естественным образом, так что вы просто делаете 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 асинхронным методом. 

[Обновление]

Ниже приведена моя попытка проиллюстрировать, что на самом деле делает async/await в этом случае. Я создал две версии одной и той же логики: WaitForItToWorkAsync (используя async/await) и WaitForItToWorkAsyncTap (используя шаблон TAP без async/await). Первая версия довольно тривиальна, в отличие от второй. Таким образом, хотя 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() может использовать потоки пула (я подозреваю, что они используют Очереди таймера ), но в этом конкретном примере (приложение WinForms) продолжение после await будет происходить в том же потоке пользовательского интерфейса. В других средах выполнения (например, консольное приложение) оно может продолжаться в другом потоке. IMO, эта статья от Стивена Клири, которую необходимо прочитать, чтобы понять концепцию многопоточности async/await.

27
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 .

1
Alessandro D'Andria

Просто предоставьте другое решение

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
Konstantin Spirin