Task status changes to RanToCompletion if the Task await's something
Solution 1
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 await
s.
Solution 2
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);
Related videos on Youtube
tomsseisums
Updated on June 06, 2022Comments
-
tomsseisums almost 2 years
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?-
StriplingWarrior almost 10 yearsDoes it have something to do with you using
Task.Factory.StartNew
to invoke an async method, rather than having that method return aTask
?
-
-
Farhad Alizadeh Noori almost 10 yearsIsn'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 callsAccept
. And that is becauseAccept
is defined as async and the task is not required to wait until it actually returns? -
tomsseisums almost 10 yearsOkay, by making
Accept()
to returnTask
instead ofvoid
and starting withoutTask.Factory
did make theTask.WhenAll
to wait until completion. The problem now, is, that the nature ofAccept
is broken - it (TcpListener) doesn't respond to more than one connection at a time - the threads are lost (as expected). -
StriplingWarrior almost 10 years@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?
-
Teoman shipahi about 7 years@FarhadAlizadehNoori seems like a good point here. Since it returns Task<Task>, wrapper task return immediately after inner task starts. Using Task.Run() will solve this problem.
-
StriplingWarrior over 2 years@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 intoAccept()
has no way of knowing whetherAccept
is markedasync
: nothing about the public signature changes if you remove theasync
keyword: only the behavior of the method itself changes.