5
void A()
{
    foreach (var document in documents)
    {
       var res = records.BulkWriteAsync(operationList, writeOptions); // res is Task<BulkWriteResult<JobInfoRecord>>
    }
}

After foreach I would like to wait the result of all BulkWriteAsync, how to do this? I don't want to mark A() as async and do the following

await records.BulkWriteAsync(operationList, writeOptions);

Is it good solution?

void A()
{
var tasks = new List<Task<BulkWriteResult<JobInfoRecord>>>();

foreach (var document in documents)
{
     var task = records.BulkWriteAsync(operationList, writeOptions);
     tasks.Add(task);
}

Task.WaitAll(tasks.ToArray());
}

I call A() in try catch if I will mark public async void A() as async I never be in catch

  • Try `Task.Wait(records.BulkWriteAsync(operationList, writeOptions));` – George Findulov Apr 04 '16 at 12:23
  • 2
    So you only want to wait for the result of the last operation or what? And why don't you want to make `A` `async`? Asynchronous call chains should really be asynchronous as far as they can go. – Luaan Apr 04 '16 at 12:23
  • 1
    Next time you should clearly express what you want to achieve in the question. That is why it's always a good idea to post a [MCVE]. – Yuval Itzchakov Apr 04 '16 at 12:24
  • @Luaan I don't want to leave the method `A()` until all `BulkWriteAsync` are not will end (will finished) –  Apr 04 '16 at 12:30
  • Do you want to wait until the end of foreach, or you need to wait each BulkWrite?What is operationList? What is document? – Glauco Cucchiar Apr 04 '16 at 12:38
  • 1
    You still didn't say why making the method `async` isn't an option for you. It should really be the default when dealing with asynchronous code. – Luaan Apr 04 '16 at 12:41
  • 1
    That just means that at some point, you break the `await` chain without handling the orphaned `Task` properly. Why do you do that? Are you using `async void` methods? Don't. If you don't need to return any data, just use `async Task` instead. – Luaan Apr 04 '16 at 12:48
  • @Luaan Can you show me please, in your answer, how to use `async-await` and don't leave `A()` until `BulkWriteAsync` finished? –  Apr 04 '16 at 12:52
  • 1
    I've added some more information. – Luaan Apr 04 '16 at 13:05
  • @ivan_petrushenko: I have written an entire [article on this subject](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). `WaitAll` and friends are some of the worst approaches. – Stephen Cleary Apr 04 '16 at 13:25
  • @StephenCleary If `WaitAll` is worst approache. What would you recommend, if I cannot use `async-await`? –  Apr 04 '16 at 13:27
  • @ivan_petrushenko: Read [my article](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). But first, think *really hard* about whether you can actually use `await` or not. Most of the time people say they "can't", it's actually that they *can* but they don't *want* to. – Stephen Cleary Apr 04 '16 at 15:21

1 Answers1

6

Well, first you want a Task that represents all the operations. The simplest way to do this is with a bit of LINQ:

Task.WhenAll(documents.Select(i => records.BulkWriteAsync(...)));

Then, you ideally want to await that task. If that isn't possible, you can try

task.GetAwaiter().GetResult();

However, make sure that none of the tasks have thread affinity - that's a great way to get a deadlock. Waiting for a task on the UI thread while the task itself needs the UI thread is a typical example.

The whole point of await is that it allows you to handle asynchronous code as if it were synchronous. So from the outside, it appears as if you never left the method until you actually get to a return (or the end of the method). For this to work, however, your method must return a Task (or Task<T>), and the callee must await your method in turn.

So a code like this:

try
{
  tasks = Task.WhenAll(documents.Select(i => ...));

  await tasks;
}
catch (Exception ex)
{
  // Handle the exception
}

will appear to run completely synchronously, and all exceptions will be handled as usual (though since we're using Task.WhenAll, some will be wrapped in AggregateException).

However, this isn't actually possible to handle with the way .NET and C# is built, so the C# compiler cheats - await is basically a return that gives you a promise of the result you'll get in the future. And when that happens, the control returns back to where the await left the last time. Task is that promise - if you use async void, there's no way for the callee to know what's happening, and it has no option but to continue as if the asynchronous method was a run-and-forget method. If you use async Task, you can await the async method and everything "feels" synchronous again. Don't break the chain, and the illusion is perfect :)

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • 1
    It is never really wise to call `Result`, `Wait` or `GetResult`. If you need it, your design is wrong. – Gusdor Apr 04 '16 at 12:49
  • 2
    @Gusdor Sometimes it's not your choice. Maybe you're called by a framework that doesn't support asynchronous methods. A synchronous wait works, as long as you make sure to avoid synchronization issues (as with any multi-threaded code). It's not desirable, of course, but sometimes you just don't have a choice. – Luaan Apr 04 '16 at 12:52
  • 1
    @ivan_petrushenko If you can't use `await`, it's passable. However, `GetAwaiter().GetResult()` will give you a lot more meaningful stack traces - it pretends as if everything was a synchronous chain of method calls, pretty much. `Wait` doesn't lie, so you might get the same stack trace as with `GetResult`, but it might also give you no useable stack trace at all. – Luaan Apr 04 '16 at 13:10