Подтвердить что ты не робот

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

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

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

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?

Обновление

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

4b9b3361

Ответ 1

Вы можете реорганизовать этот фрагмент следующим образом:

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 pattern без async/await). Версия frist довольно тривиальна, в отличие от второй. Таким образом, в то время как 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 произойдет в одном и том же потоке пользовательского интерфейса. В других средах исполнения (например, консольном приложении) он может продолжаться в другом потоке. IMO, эта статья Стивена Клири является обязательным для понимания концепциями потоковой передачи async/await.

Ответ 2

Если задача асинхронная, вы можете попробовать:

    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.

Ответ 3

Вам действительно не нужен метод 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
}

Ответ 4

Просто укажите другое решение

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);
            }
        });
    }