Существует ли стандартный шаблон для ожидания, когда N номеров асинхронных методов будет завершено?

public class FooDataRepository
{
    private MyServiceReferenceClient Client { get; set; }

    public void FooClass()
    {
        Client = new MyServiceReferenceClient();
        Client.SaveFooCompleted += Client_SaveFooCompleted;
        Client.SaveBarCompleted += Client_SaveBarCompleted;
    }

    private void Client_SaveFooCompleted(Object sender, EventArgs e) { ... }

    private void Client_SaveBarCompleted(Object sender, EventArgs e) { ... }

    public void SaveFoo(Foo foo)
    {
        Client.SaveFooAsync(foo);

        foreach (var bar in foo.Bars)
            Client.SaveBarAsync(bar);
    }

}

Я хочу что-то сделать в классе FooDataRepository после завершения SaveFooAsync и всех методов SaveBarAsync. Есть ли стандартный шаблон для попытки сделать одну вещь, основанную на N числе вызовов асинхронных вызовов?

Ответ 1

Вы можете использовать TaskFactory.ContinueWhenAll, чтобы запланировать запуск кода, когда все задачи завершены.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//This will not block.
Task.Factory.ContinueWhenAll(tasks, completedTasks => { RunSomeMethod(); });

EDIT:

Что касается вопроса о создании вызовов асинхронного метода с задачами, если у класса есть методы Begin/End для вызова метода с помощью async, вы можете использовать Task. FromAsync

В качестве альтернативы вы также можете использовать Rx для асинхронного вызова нескольких методов, а затем наблюдения, когда все они завершены. Посмотрите на этот вопрос: Могу ли я сократить шаблон асинхронизации Begin/End, используя событие для создания моего AsyncResult? (С#.Net 3.5, WCF)

Ответ 2

Если вы можете, используйте Задачи, а затем выполните Task.WaitAll. Например:.

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...

Если у вас нет доступа к синхронным методам, вы можете использовать метод, подобный описанному в Задачи и Асинхронный шаблон на основе событий конвертировать вашу библиотеку EAP в ту, которая использует задачи. В качестве альтернативы Rx предоставляет несколько способов решения этой проблемы.

В принципе, советы по лучшей практике - использовать Задачи, если сможете. Если ваше приложение требует более мелкомасштабного управления, посмотрите в Rx серьезно - он обеспечивает фильтрацию LINQ для событий и асинхронных методов.

Ответ 3

Я не знаю, как "стандартная" моя идея, но я недавно начал использовать некоторые асинхронные вызовы с службами данных WCF и Silverlight. В некоторых случаях у нас есть коллекции, которые очень похожи на "коллекции", созданные шаблоном репозитория. Мы выдаем запрос на получение элементов из службы данных WCF, но затем каждый возвращаемый элемент должен быть "загружен" по очереди (т.е. Каждый элемент имеет метод загрузки, который выполняется асинхронно и может в конечном итоге выдать свой собственный запрос службы данных WCF), Во время этой загрузки любой из наших gui, который зависит от загружаемых данных (некоторые вкладки), "заблокирован" (индикатор прогресса отображается), пока он не будет загружен. В некоторых случаях нам приходится загружать две коллекции, потому что у них есть отношение к другому. Мы используем шаблон обратного вызова с помощью службы данных WCF, поэтому после вызова каждого вызова (при загрузке нескольких коллекций) мы знаем, что наша задача "загрузки" завершена.

Итак, применение нашего шаблона к вашему делу приведет к чему-то вроде этого (грубый псевдокод с использованием кода примера в качестве отправной точки)

public class FooDataRepository 
{
     bool fooCompleted = false;
     bool barCompleted = false;
     int barsSaved = 0;
     int barCount = 0;

     private MyServiceReferenceClient Client { get; set; }
     public void FooClass()
     {
         Client = new MyServiceReferenceClient();
         Client.SaveFooCompleted += Client_SaveFooCompleted;
         Client.SaveBarCompleted += Client_SaveBarCompleted;
     }

     private void Client_SaveFooCompleted(Object sender, EventArgs e) 
     {
       fooCompleted = true;
       if (barCompleted)
       {
         SaveCompleted();
       }
     }

     private void Client_SaveBarCompleted(Object sender, EventArgs e) 
     {
       Interlocked.Increment(barsSaved);
       barCompleted = barsSaved == barCount;
       if (fooCompleted)
       {
         SaveCompleted();
       }
     }

     private void SaveCompleted()
     {
       //Do whatever you want to do when foo and all bars have been saved
     }

     public void SaveFoo(Foo foo)
     {
        fooCompleted = barCompleted = false;
        barCount = foo.Bars.Count;
        barsSaved = 0;

        Client.SaveFooAsync(foo);
        foreach (var bar in foo.Bars)
            Client.SaveBarAsync(bar);
     }
 } 

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

Я много не использовал Задачи и вообще не использовал Rx, поэтому я не знаю, как они применяются или нет к этой проблеме.

Удачи!