简体   繁体   中英

Unexpected result while using async await

class Program
{
    static void Main(string[] args)
    {
        var a = new A();

        var result = a.Method();

        Console.Read();
    }
}

class A
{
    public async Task<string> Method()
    {
        await Task.Run(new Action(MethodA));
        await Task.Run(new Action(MethodB));

        return "done";
    }

    public async void MethodA()
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("MethodA" + i);
            await Task.Delay(1000);
        }
    }

    public async void MethodB()
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("MethodB" + i);
            await Task.Delay(1000);
        }
    }
}

Result Output

在此处输入图片说明

I think the expectation here is first output MethodA1, MethodA2... MethodA9 and then start to output MethodB1... MethodB9.

But seems it's not. Does anyone have an explanation on this? Thanks!


Edit:

Thanks for the replies. Actually I know how to output the correct result but just wonder why my code behave weird as I thought it against my knowledge of async/await.

class A
{
    public async Task<string> Method()
    {
        await Task.Run(new Action(MethodA));

        await Task.Run(async () => {
            for (var i=0;i<10;i++)
                await Task.Delay(100);
            Console.WriteLine("should output before done");
        });

        return "done";
    }

    public async void MethodA()
    {
        var returnString = string.Empty;
        for (var i = 0; i < 10; i++)
        {
            await Task.Delay(200);
        }

        Console.WriteLine("MethodA " + Thread.CurrentThread.ManagedThreadId);

    }
}

I simplified the code and now it's like this. The first await quickly shift control to the next await and then the second await behaves as I expected and "done" was output at last. But I don't know what made the difference here.

Result Output

在此处输入图片说明

Updates: Do remember the sample code is a bad practice. you could read this post for more information

And a friend suggested the difference is that

await Task.Run(new Action(MethodA));

doesn't equals

 await Task.Run(async () => {
            for (var i=0;i<10;i++)
                await Task.Delay(100);
            Console.WriteLine("should output before done");
        });

the former one invoked 'methodA' in a synchronized way while the poster one is a correct asynchronized invoke. But it still confuses me because if this is true then the asynchronized action can and only can be represented by a lambda expression. I mean, there is not a thing like 'new async Action(...)' right?

Updates:

I tried Task.Factory.StartNew(Action action) which was believed to be equivalent to Task.Run(Action action) and things changed again:

class A
{
    public async Task<string> Method()
    {
        await Task.Factory.StartNew(new Action(MethodA));

        await Task.Factory.StartNew(async () => {
            for (var i=0;i<10;i++)
                await Task.Delay(100);
            Console.WriteLine("should output before done");
        });

        return "done";
    }

    public async void MethodA()
    {
        for (var i = 0; i < 10; i++)
            await Task.Delay(200);
        Console.WriteLine("MethodA");

    }
}

I guess I need more help on this...

The reason you code behaves as it does is because the Task you start up to call one of the methods will finish on the first await in the method because it is async void and just continue thus causing the await Task.Run(new Action(MethodA)); to return control to the thread while your MethodA body is still running. This is because async void is basically fire and forget.

Here's what happens in a simplified version of your code.

class A
{
    public async Task<string> Method()
    {
        // You create a task to run MethodA
        await Task.Run(new Action(MethodA));
        // control returns here on the first `await` in MethodA because 
        // it is `async void`. 

        return "done";
    }

    public async void MethodA()
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("MethodA" + i);
            // Here the control is returned to the thread and because 
            // it's `async void` an `await` will only block until you reach
            // this point the first time.
            await Task.Delay(1000); // 
        }
    }
}

To get the results you want you first need to return a Task from your methods so they can be awaited. Then you don't need the Task.Run either.

class A
{
    public async Task<string> Method()
    {
        await MethodA();
        await MethodB();

        return "done";
    }

    public async Task MethodA()
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("MethodA" + i);
            await Task.Delay(1000);
        }
    }

    public async Task MethodB()
    {
        for (var i = 0; i < 10; i++)
        {
            Console.WriteLine("MethodB" + i);
            await Task.Delay(1000);
        }
    }
}

You probably want to return Task from MethodA and MethodB, instead of writing to the console IN those methods. Like this:

class Program
{
    static void Main(string[] args)
    {
        var a = new A();

        var result = a.Method();

        Console.Read();
    }
}

class A
{
    public async Task<string> Method()
    {
        Console.WriteLine(await MethodA());
        Console.WriteLine(await MethodB());

        return "done";
    }

    public async Task<string> MethodA()
    {
        var returnString = string.Empty;
        for (var i = 0; i < 10; i++)
        {
            returnString += "MethodA" + i + '\n'+'\r';
            await Task.Delay(100);
        }
        return returnString;
    }

    public async Task<string> MethodB()
    {
        var returnString = string.Empty;
        for (var i = 0; i < 10; i++)
        {
            returnString += "MethodB" + i + '\n'+'\r';
            await Task.Delay(100);
        }
        return returnString;
    }
}

Note: I haven't tested that code, but it should illustrate the general idea.

async + await != sync , because of continuation

For more details, Asynchronous Programming with Async and Await (C# and Visual Basic)

Async methods are intended to be non-blocking operations.

An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM