24

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?

Community
  • 1
  • 1
tomsseisums
  • 13,168
  • 19
  • 83
  • 145
  • 1
    Does it have something to do with you using `Task.Factory.StartNew` to invoke an async method, rather than having that method return a `Task`? – StriplingWarrior May 29 '14 at 15:09

3 Answers3

20

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.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • 1
    Isn't it more correct to say that: it is not the `Accept` method that returns immediately, but the task that does the calling finishes as soon as it calls `Accept`. And that is because `Accept` is defined as async and the task is not required to wait until it actually returns? – Farhad Alizadeh Noori May 29 '14 at 16:29
  • Okay, by making `Accept()` to return `Task` instead of `void` and starting without `Task.Factory` did make the `Task.WhenAll` to wait until completion. The problem now, is, that the nature of `Accept` is broken - it (TcpListener) doesn't respond to more than one connection at a time - the threads are lost (as expected). – tomsseisums May 29 '14 at 18:41
  • @jolt: There's a lot of code in there, and I'm not entirely sure what you're going for. Could you narrow down your new problem to something more manageable and ask it as a new question? – StriplingWarrior May 29 '14 at 23:17
  • @FarhadAlizadehNoori seems like a good point here. Since it returns Task, wrapper task return immediately after inner task starts. Using Task.Run() will solve this problem. – Teoman shipahi Mar 08 '17 at 20:00
  • @FarhadAlizadehNoori: No. If you look at the code generated when `Accept` method is compiled, the method itself just builds a state machine with some Task continuations, and then returns control back to the calling method before those continuations have executed. The code calling into `Accept()` has no way of knowing whether `Accept` is marked `async`: nothing about the public signature changes if you remove the `async` keyword: only the behavior of the method itself changes. – StriplingWarrior Aug 04 '21 at 17:48
  • 1
    I was having a similar issue with `Task.Run()` of an async method. This explained the issue perfectly; by using Task.Run I'm basically nesting async calls, and the one I actually want to monitor is a layer deeper in the stack than the Task reference I get from `Task.Run()`. Lesson learned, don't mix paradigms; just call the async method and watch the Task you get from it. – KeithS Jul 06 '23 at 16:18
12

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);
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

I had a similar issue.

I have a reader task which I want to trigger to read data and process it. Therefore I used:

var readTrigger = new ManualResetEventSlim(); 

In order to gracefully stop the thread whenever it is required I used a CancellationToken. Since I didn't want my task to continuously run and poll data (but use events to signalize new data) I used a blocking function to put the task to sleep when there was nothing to do. Thus I had to listen to both events and this was done using a WaitHandle.

var action = WaitHandle.WaitAny(new WaitHandle[] { readTrigger.WaitHandle, cancellationToken.WaitHandle });

There might be a different, better solution with the WaitHandle with async/await (If somebody knows it please tell me :) )

The function still contains async functions though that I had to wait on. This resulted in a thread function which is async which had the following function header:

private async Task ReadDataTask(CancellationToken cancellationToken)

The WaitHandle function does blocked waiting until at least one of the events gets triggered. And since I was using WaitHandle I need to use a separate thread to prevent blocking my whole application. I used the following call to start my Task in a separate thread:

var t = new Task(async() => await ReadDataTask(ct), ct, TaskCreationOptions.LongRunning);

There I had the same problem, that task t terminated before the ReadDataTask function returned. Since I had to use the WaitHandle (or lets say wanted) non of the former solutions worked for me.

The solution to my problem was fairly simple. I didn't use await to wait for my ReadDataTask but used the Wait from the returned task, which resulted in the following call:

var t = new Task(() => ReadDataTask(ct).Wait(), ct, TaskCreationOptions.LongRunning);

I hope I could help you with my solution.

Kind Regards