jeff.eynon
jeff.eynon

Reputation: 1326

Chaining async calls together - call never returns

I have a hierarchy of async calls I'm making. Looks basically like this:

async MyObject MainWorker()
{
    return await Task.Run(SomeSynchronousFunction);
}

async MyObject InbetweenFunction1()
{
    //do some things
    return await MainWorker();
}

async MyObject InbetweenFunction2()
{
    //do some things 
    return await MainWorker();
}

void ReturnObject TopFunction()
{
    Task<MyObject> return1 = InbetweenFunction1();
    Task<MyObject> return2 = InbetweenFunction2();

    while (!return1.IsComplete || !return2.IsComplete)
         Thread.Sleep(100);

    //access return1.Return and return2.Return values and return from this function
}

So I have a few levels of async calls. The top level method makes two async calls, waits for them to complete (via polling) and then accesses their return values and does something with the values. The problem is, none of the async calls ever finish. The function SomeSynchronousFunction which is called asynchronously with Task.Run should take a few seconds tops, but I wait 30+ seconds and no results.

This is my first attempt at using the new async/await keywords. Am I doing something obviously wrong?

Upvotes: 0

Views: 536

Answers (3)

svick
svick

Reputation: 245046

Am I doing something obviously wrong?

Yes, you're doing busy waiting (looping until a condition becomes true), which you shouldn't do unless there is no other option. And there is a better option here: use Wait(), or Task.WaitAll().

But doing that most likely wouldn't actually make your code work. The real problem is probably the classic await deadlock: you're in a synchronization context (commonly either the UI thread or the ASP.NET request context) and your awaits try to resume on that context, but you're also blocking that context waiting on the Tasks to finish.

The right way to solve this would be to use await in TopLevelFunction() too. This would require you to make that function async too, which then means its caller now has to be async also, etc. This is called “async all the way”.

All the async methods should be async Task methods, with the exception of the top level event handlers, which has to be async void. (But you shouldn't use async void methods anywhere else, because they can't be awaited.)

This means your function would become:

async Task<ReturnObject> TopFunction()
{
    Task<MyObject> return1 = InbetweenFunction1();
    Task<MyObject> return2 = InbetweenFunction2();

    await Task.WhenAll(return1, return2);

    //access await return1 and await return2 values and return from this function
}

Upvotes: 4

bradgonesurfing
bradgonesurfing

Reputation: 32202

What you want is an extra function to combine the previous tasks

public async Task<string> Combine(Task<string> a, Task<string b){
    var aa = await a;
    var bb = await b;
    return aa + bb; 
}

and

public string TopFunction()
{
    var task = Combine(InBetweenFunction2(), InVetweenFunction2());

    // Blocking call
    // Really you shouldn't be doing this unless you really really
    // have to.
    task.Wait();

    return task.Result;
}

Note one of the places you really really have to use blocking calls is in the entry point main function of the application which can't be declared async and thus can't use await

Upvotes: 0

Stephen Zeng
Stephen Zeng

Reputation: 2818

Here is some workable code:

    private async Task<string> DoMainWork()
    {
        await Task.Delay(3000);
        return "MainWorker";
    }

    private async Task<string> InbetweenFunction1()
    {
        // do something
        await Task.Delay(1000);
        return await DoMainWork() + "1";
    }

    private async Task<string> InbetweenFunction2()
    {
        // do something
        await Task.Delay(2000);
        return await DoMainWork() + "2";
    }

    public string TopFunction()
    {
        string return1 = null;
        string return2 = null;

        var taskList = new List<Task>();
        taskList.Add(Task.Run(async () => return1 = await InbetweenFunction1()));
        taskList.Add(Task.Run(async () => return2 = await InbetweenFunction2()));

        Task.WaitAll(taskList.ToArray());

        return return1 + return2;
    }

Upvotes: 1

Related Questions