Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task status changes to RanToCompletion if the Task await's something

The question describes the same problem found here - MSDN Developer Forum. The question does not have an accepted answer, neither any of the answers given can be applied to my case (hence a different question).

Question is also derived from one I asked previously, but, due to different nature and more specific problem, I'm asking a new one.

Full code can be found here: http://pastebin.com/uhBGWC5e
* Only thing changed is the task completion check (while -> Task.WhenAll).


When awaiting an async operation inside of a Task, the Task status changes to RanToCompletion even though, the Task is still running.

Now, let's see the setup:

// Start async.
Task t1 = Task.Factory.StartNew(Accept, s1);
Task t2 = Task.Factory.StartNew(Accept, s1);

Task.WhenAll(t1, t2).Wait();

The Accept method:

public static async void Accept(object state)
{
    TcpListenerEx server = (TcpListenerEx) state;

    IPEndPoint endPoint = server.LocalEndpoint as IPEndPoint;

    Log("Accepting clients on {0}", endPoint);

    while (true)
    {
        var client = server.AcceptTcpClientAsync();

        if (client == null)
        {
            Log("Null error on accept");
            break;
        }

        TcpClient connected = (TcpClient) client;
        servers[server].Add(connected);

        bool stop = await Task<Task<bool>>.Factory.StartNew(Listen, connected).Unwrap();

        if (stop == true)
        {
            break;
        }
    }

    // Stop the server.
    server.Stop();

    Log("Stoppped {0}", endPoint);
}

Because of TaskStatus changing to RanToCompletion, the Task.WhenAll().Wait() call marks itself finished fairly quickly, resulting in program to be executed further, eventually - terminated.

But, the Accept task, in theory, should never stop, it's listening for connections until explicitly stopped.

What is the problem here that's causing the Task to be marked as RanToCompletion prematurely?

like image 256
tomsseisums Avatar asked May 29 '14 15:05

tomsseisums


2 Answers

I can reproduce this issue with far less code:

void Main()
{
    Task t1 = Task.Factory.StartNew(Accept);
    t1.Wait();
    Console.WriteLine("Main ended");
}

public static async void Accept()
{
    while (true)
    {
        await Task.Delay(1000);
    }

    Console.WriteLine("Stoppped");
}

But this works correctly:

void Main()
{
    Task t1 = Accept();
    t1.Wait();
    Console.WriteLine("Main ended");
}

public static async Task Accept()
{
    while (true)
    {
        await Task.Delay(1000);
    }

    Console.WriteLine("Stoppped");
}

Basically, by using Task.Factory.StartNew(), you are creating a Task based on a separate thread getting spawned to invoke the given delegate (the Accept() method). The Accept method itself (like any good async method) actually returns immediately. So the thread that calls it finishes its task immediately, so the Task created to represent that thread also finishes immediately.

If you allow Accept() to return a Task instead of void, then the Task that it returns is what you should be awaiting if you want to wait until it has run through all its awaits.

like image 63
StriplingWarrior Avatar answered Nov 03 '22 03:11

StriplingWarrior


There are two things wrong: async void and Task.Factory.StartNew. Both of these are bad practices.

First, async void does not allow the calling code to know when it completes. So it doesn't matter that you're waiting; the async void method will return fairly quickly, and your app can't know when Accept actually finishes. To fix this, replace async void with the much more proper async Task.

Second, StartNew doesn't understand asynchronous delegates. StartNew is an extremely low-level API that should not be used in 99.99% of production code. Use Task.Run instead.

public static async Task Accept(object state);

Task t1 = Task.Run(() => Accept(s1));
Task t2 = Task.Run(() => Accept(s1));
Task.WaitAll(t1, t2);
like image 11
Stephen Cleary Avatar answered Nov 03 '22 01:11

Stephen Cleary