Какво да правя с асинхронни Task-ове, които не искам да изчаквам?

+7 гласа
108 прегледа
попитан 2016 юни 20 от Nikoleta.V. (4,090 точки)

Правя сървъра на мултиплеър игра и опитвам да разбера как да използвам функциите на async/await. Ядрото на сървъра е цикъл, който обновява участниците в играта колкото може по-бързо  

while (!shutdown) 

    foreach (var actor in actors) 

        actor.Update(); 

Цикълът трябва да обработва хиляди участници много пъти в секунда, за да върви играта гладко. Някои участници обаче изпълняват бавни задачи в тяхното обновяване, като взимане на дании от базата, което искам да става асинхронно. След като се вземат тези данни, участникът трябва да се обнови, което става в основната нишка. 

Тъй като е конзолно приложение, искам да напиша SynchronizationContext, който да изпраща делегати в основния цикъл. Това ще позволи на задачите да обновяват играта веднага щом завършат и необработени изключения ще се хвърлят в основния цикъл. Въпросът ми е, как да напиша асинхронно функциите за обновяване? Това работи добре, но не спазва препоръката да не се използва async void: 

Thing foo; 

public override void Update() 

    foo.DoThings(); 

    if (someCondition) { 

        UpdateAsync(); 

    } 

async void UpdateAsync() 

    // взима данни, но сървъра си продължава през това време 

    var newFoo = await GetFooFromDatabase(); 

    // в основната нишка обновява състоянието на играта 

    this.foo = newFoo; 

Мога да направя Update() с async и да пращам задачите в основия цикъл, но: 

  • Не искам да натоварвам хилядите обновления,които няма да го използват 
  • Дори в основния цикъл не искам да await-вам тези задачи и това ще блокира цикъла 
  • Изчакването на задачата ще причини deadlock, тъй като трябва да се завърши на изчакващата нишка 

Какво да правя със задачите, които не мога да await-на? Единствено когато изключвам сървъра ще искам да знам, че всички са завършили, но не искам да съхранявам всяка една задача, която се генерира. 

1 отговор

0 гласа
отговорени 2016 юни 21 от valeri.hristov (7,340 точки)

Доколкото разбирам, проблемът е, че искаш следното:

while (!shutdown)

{

     // Това трябва да се случи веднага и приключването се появява в главната нишка

    foreach (var actor in actors)

        actor.Update(); //включва i/o операции с базата

    // последващия код не трябва да се забавя

   ...

}

Този while цикъл е доста еднонишков. Можеш да пуснеш foreach-а да върви паралелно, но тогава може да да трябва да изчакваш най-продължителната инстанция(i/o операцията за взимане от базата).

Await/async не е опция в този цикъл, трябва да пуснеш тези задачи с дърпането от базата в thread pool-а. В него async/await ще е полезен за освобождаване на нишки.

Относно как да връщаш готовите в основната нишка. Прочети това:

msdn.microsoft.com/en-us/magazine/cc163417.aspx

Може да е малко тегаво обаче. Можеш да имаш опашка с готовите, която проверяваш с основната нишка на всяко минаване през while цикъла. Трябва да ползваш конкурентна структура от данни:

msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

Някои съвети:

  • Ако нямаш Wait по-нагоре от задачата, основната нишка на конзолата ще приключи и ще затвори и приложението
  • Както и ти каза, async/await не блокира текущата нишка, но това значи, че кодът след await ще се изпълни когато завърши await-ваната задача.
  • Synchronization Context е null в конзолни приложения :

http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

  • Async не се ползва за операции, които пускаш и забравяш за тях. За такива операции според ситуацията:
  1. Използваш Task.Run или Task.StartNew
  2. Използваш шаблон тип producer/consumer

Внимавай за следното:

  • Ще трябва да обработваш изключения в тасковете/нишките. Дори и само за да логнеш, че ги е имало.
  • Ако процесът умре докато тези продължително задачи вървят или изчакват, ще имаш нужда от някакъв механизъм, който се грижи за съхраняването на състоянието им (база данни, външна опашка, пазене във файлове) и в последствие продължаването им.

Ако искаш да им знаеш състоянието трябва да го пазиш някъде. Хубавото на този механизъм е, че е устойчив на крашвания и като нещо изгасне после можеш да си продължиш откъдето си бил.

...