2013-09-03 4 views
17

Мне нужно написать некоторый асинхронный код, который по существу пытается повторно поговорить и инициализировать базу данных. Довольно часто первая попытка не срабатывает, поэтому требуется повторить попытку.Лучший асинхронный метод 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?

Update

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

ответ

25

Вы можете реорганизовать этот фрагмент так:

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 линия будет продолжена асинхронно. Here's возможно лучший объяснение о том, как это делается внутренне. Дело в том, что обычно в какой-то момент вашего кода вам нужно синхронизировать результат асинхронной задачи. Например .:

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 на самом деле делает в этом случае. Я создал две версии той же логики, 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, this article от Stephen Cleary является обязательным для понимания концепциями потоковой передачи async/await.

+0

Я бы назвал это просто, выполнив 'this.WaitForItToWork();' - будет ли библиотека async заботиться о потоке для меня? – Chris

+1

Вы бы назвали это как 'await this.WaitForItToWork()', и вся цепочка звонков должна быть реорганизована для поддержки этого ... Я расскажу о своем ответе, чтобы включить в него больше информации. – Noseratio

+0

@ Крис: вы должны помнить, что используйте ключевое слово «ожидание». Правило большого пальца: всегда «ожидание» должно сочетаться с функцией «async». Итак, вы должны сделать что-то вроде WaitForItToWork(); –

0

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

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

Это именно то, чего я хотел избежать своим ответом. Возможно, я сам что-то пропустил:] – Noseratio

+0

@Noseratio Метод async немедленно вернется, когда будет поражен ожидаемый. Компилятор C# выполняет всю синхронизацию потоков для вас. http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx Важно: «Ожидание выражения не блокирует поток, на котором он выполняется. Вместо этого он заставляет компилятор регистрировать оставшуюся часть асинхронного метода как продолжение на ожидаемой задаче». – Gusdor

+0

Я ошибаюсь. Chillax. Никакой вред в исправлении или добавлении ясности, даже после факта. Я редактировал, чтобы избежать путаницы со стороны других читателей. Я не редактировал, чтобы вы выглядели глупо. Это нарушит принцип DRY./sickburn – Gusdor

0

Вам не нужно 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

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

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); 
      } 
     }); 
    } 
Смежные вопросы