2015-06-30 3 views
1

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

Result result1 = null; 
Result result2 = null; 

var task1 = Task.Factory.StartNew(()=> 
{ 
    var records = DB.Read(".."); 
    //Do A lot 
    result1 = Process(records); 
}); 

var task2 = Task.Factory.StartNew(()=> 
{ 
    var records = DB.Read("....."); 
    //Do A lot 
    result2 = Process(records); 
}); 

Task.WaitAll(task1, task2); 

var result = Combine(result1, result2); 

Теперь мы хотели бы использовать асинхронные аналоги DB функций, и мы используем эту новую модель:

Result result1 = null; 
Result result2 = null; 

var task1 = await Task.Factory.StartNew(async()=> 
{ 
    var records = await DB.ReadAsync(".."); 
    //Do A lot 
    result1 = Process(records); 
}); 

var task2 = await Task.Factory.StartNew(async()=> 
{ 
    var records = await DB.ReadAsync("....."); 
    //Do A lot 
    result2 = Process(records); 
}); 

Task.WaitAll(task1, task2); 

var result = Combine(result1, result2); 

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

+2

Какое ненормальное поведение вы говорите конкретно? Перепутались ли задачи друг с другом? Не работали ли задания? –

+2

Ваши 'result' должны возвращать значения задачи, а не закрывать переменные. Верните их из задач. – SLaks

+1

Я предполагаю, что метод, который ждет на task1/2, немедленно вернется, что можно назвать ненормальным. –

ответ

2

Task.Factory.StartNew является асинхронным API. Вы должны использовать Task.Run, которая была разработана с асинхронным-ждать в виду:

var task1 = await Task.Run(async()=> 
{ 
    var records = await DB.ReadAsync(".."); 
    //Do A lot 
    result1 = Process(records); 
}); 

Этот вопрос о том, что асинхронная лямбда возвращает Task так Task.Factory.StartNew возвращает Task<Task> (внешний один, потому что Task.Factory.StartNew возвращает Task и внутренний один который является результатом асинхронной лямбда).

Это означает, что, когда вы ждете на task1 и task2, вы не очень ожидаете всей операции, только синхронной ее части.

Вы можете исправить это с помощью Task.Unwrap на возвращаемом Task<Task>:

Task<Task> task1 = await Task.Factory.StartNew(async()=> 
{ 
    var records = await DB.ReadAsync(".."); 
    //Do A lot 
    result1 = Process(records); 
}); 

Task actualTask1 = task1.Unwrap(); 
await actualTask1; 

Task.Run Но делает это неявно для вас.


В качестве примечания, вы должны понимать, что вам не нужно Task.Run выполнять эти операции одновременно. Вы можете сделать это только с помощью вызова этих методов и ожидают результаты вместе с Task.When:

async Task MainAsync() 
{ 
    var task1 = FooAsync(); 
    var task2 = BarAsync(); 
    await Task.WhenAll(task1, task2); 

    var result = Combine(task1.Result, task2.Result); 
} 

async Task<Result> FooAsync() 
{ 
    var records = await DB.ReadAsync(".."); 
    //Do A lot 
    return Process(records); 
} 

async Task<Result> BarAsync() 
{ 
    var records = await DB.ReadAsync("....."); 
    //Do A lot 
    return Process(records); 
} 

Вам нужно только Task.Run, если вам нужно, чтобы разгрузить даже синхронные части этих методов (часть перед первым await) к ThreadPool.

+0

Почему бы просто не использовать 'await DB.ReadAsync()' без 'Task.Run' wrappers? – Noseratio

+0

@Noseratio: право, вы все еще можете. Но, похоже, Op больше озабочен использованием 'Task', по какой-то причине, асинхронным вызовом. – Tigran

+0

@Tigran, я бы использовал асинхронные API без упаковки, а затем обернул вызовы 'Process' с помощью' Task.Run' - если они действительно интенсивно работают в ЦП. – Noseratio

0

Task.Factory.StartNew начало новой задачи исполнения другое независимое исполнительный блок. Таким образом, самый простой способ справиться с этим может выглядеть следующим образом:

var task1 = Task.Factory.StartNew(()=> //NO AWAIT 
{ 
    var records = DB.Read("....."); //NO ASYNC 
    //Do A lot 
    result1 = Process(records); 
}); 

... another task definition 

Task.WaitAll(task1, task2); 

Рид и процесс последовательно в одной задаче, так как у вас есть зависимость данных.

+1

StartNew не поддерживает методы async, он не разворачивает внутреннюю задачу. Вы действительно должны использовать 'Task.Run (' вместо этого. Пожалуйста, прочитайте «[StartNew is Dangerous] (http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html)» от Stepen Cleary –

+0

I не используйте метод async, так как вы можете видеть, читаете ли вы код. – Tigran

+1

'var task1 = Task.Factory.StartNew (async() =>' вы используете async прямо там. 'task1' будет' Task ' и ваш 'WaitAll' не будет работать так, как вы ожидаете. Чтобы использовать' WaitAll', как вы хотите, вам сначала нужно вызвать '.Unwrap()' в задаче (или использовать 'Task.Run (' который вызывает разворот для вас). –

0

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

Task.Run может развернуть асинхронную лямбду для вас. Но есть более простой и понятный способ.

var task1 = DB.ReadAsync("..").ContinueWith(task => { 
    //Do A lot 
    return Process(task.Result); 
}, TaskScheduler.Default); 

var task2 = DB.ReadAsync("..").ContinueWith(task => { 
    //Do A lot 
    return Process(task.Result); 
}, TaskScheduler.Default); 

var result = Combine(await task1, await task2); 

Таким образом, вы получите результат именно тогда, когда он будет готов. Таким образом, вам не нужны дополнительные задачи и переменные.

Обратите внимание: ContinueWith является сложной функцией, и она работает на TaskScheduler.Current, если она не является нулевой, и в противном случае она работает на TaskScheduler.Default, который является планировщиком пула потоков. Поэтому безопаснее всегда указывать планировщик при вызове этой функции.

Также для claryfing я не включал проверку ошибок, потому что на самом деле DB.ReadAsync может быть завершен с ошибкой. Но это легко, и вы можете справиться с этим сами.

+1

Параметр в 'ContinueWith' является антецедентом' Task'. – i3arnon

Смежные вопросы