Mixxiphoid
Mixxiphoid

Reputation: 1034

Do I create a deadlock for Task.WhenAll()

I seem to be experiencing a deadlock with the following code, but I do not understand why.
From a certain point in code I call this method.

public async Task<SearchResult> Search(SearchData searchData)
{
    var tasks = new List<Task<FolderResult>>();

    using (var serviceClient = new Service.ServiceClient())
    {
        foreach (var result in MethodThatCallsWebservice(serviceClient, config, searchData))
                tasks.Add(result);
        return await GetResult(tasks);
    }

Where GetResult is as following:

private static async Task<SearchResult> GetResult(IEnumerable<Task<FolderResult>> tasks)
{
    var result = new SearchResult();
    await Task.WhenAll(tasks).ConfigureAwait(false);
    foreach (var taskResult in tasks.Select(p => p.MyResult))
    {
        foreach (var folder in taskResult.Result)
        {
            // Do stuff to fill result
        }
    }
    return result;
}

The line var result = new SearchResult(); never completes, though the GUI is responsive because of the following code:

    public async void DisplaySearchResult(Task<SearchResult> searchResult)
    {
        var result = await searchResult;
        FillResultView(result);
    }

This method is called via an event handler that called the Search method.

_view.Search += (sender, args) => _view.DisplaySearchResult(_model.Search(args.Value));

The first line of DisplaySearchResult gets called, which follows the path down to the GetResult method with the Task.WhenAll(...) part.

Why isn't the Task.WhenAll(...) ever completed? Did I not understand the use of await correctly? If I run the tasks synchronously, I do get the result but then the GUI freezes:

foreach (var task in tasks)
    task.RunSynchronously();

I read various solutions, but most were in combination with Task.WaitAll() and therefore did not help much. I also tried to use the help from this blogpost as you can see in DisplaySearchResult but I failed to get it to work.

Update 1: The method MethodThatCallsWebservice:

private IEnumerable<Task<FolderResult>> MethodThatCallsWebservice(ServiceClient serviceClient, SearchData searchData)
{
    // Doing stuff here to determine keys
   foreach(var key in keys)
        yield return new Task<FolderResult>(() => new FolderResult(key, serviceClient.GetStuff(input))); // NOTE: This is not the async variant
}

Upvotes: 2

Views: 2725

Answers (2)

NeddySpaghetti
NeddySpaghetti

Reputation: 13495

You need to start your tasks before you return them. Or even better use Task.Run.

This:

    yield return new Task<FolderResult>(() => 
                 new FolderResult(key, serviceClient.GetStuff(input))) 
                // NOTE: This is not the async variant

Is better written as:

yield return Task.Run<FolderResult>(() => 
             new FolderResult(key, serviceClient.GetStuff(input)));

Upvotes: 1

i3arnon
i3arnon

Reputation: 116548

Since you have an asynchronous version of GetStuff (GetStuffAsync) it's much better to use it instead of offloading the synchronous GetStuff to a ThreadPool thread with Task.Run. This wastes threads and limits scalability.

async methods return a "hot" task so you don't need to call Start:

IEnumerable<Task<FolderResult>> MethodThatCallsWebservice(ServiceClient serviceClient, SearchData searchData)
{
    return keys.Select(async key => 
        new FolderResult(key, await serviceClient.GetStuffAsync(input)));
}

Upvotes: 3

Related Questions