dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.55k stars 4.54k forks source link

Task Status #103428

Open Mr0N opened 3 weeks ago

Mr0N commented 3 weeks ago

Description

In the tasks, for some reason, the status shows that they have already been completed, but they are still running. Because of this, Task.WaitAll does not stop them, as it thinks they have already finished. This is a strange behavior. image

Reproduction Steps

using System;
using System.Collections.Concurrent;

var response = Enumerable.Range(0, 1000);

var res = response.Chunk(100);

var ls = new List<Task>();
CancellationTokenSource factory = new();
ConcurrentBag<string> info = new ConcurrentBag<string>();
foreach (var element in res)
{

    var task = new Task(async () =>
    {
        await Task.Delay(1000000, factory.Token)
                        .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
        foreach (var item in element)
        {
            info.Add(item.ToString());

            Console.Write(item + "|");
        }
    }, TaskCreationOptions.LongRunning);
    ls.Add(task);
    task.Start();
}
await Task.Delay(3000);
factory.Cancel();
Task.WaitAll(ls.ToArray());
Console.WriteLine($"Counts Distinct Items:{info.Distinct().Count()} Count {info.Count}");
Console.ReadKey();

Expected behavior

-

Actual behavior

-

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

dotnet-policy-service[bot] commented 3 weeks ago

Tagging subscribers to this area: @dotnet/area-system-threading-tasks See info in area-owners.md if you want to be subscribed.

stephentoub commented 3 weeks ago

The problem is in the repro code. If you have code like this:

var task = new Task(async () =>
{
    A();
    await Task.Delay(1000);
    B();
});
task.Start();

that compiles down to using the Task constructor that takes an Action, with the async lambda becoming an async void. That Action will return the moment it awaits something that's not yet complete. So in my above example, that Task will complete the moment it hits that await.

Mr0N commented 3 weeks ago

The problem is in the repro code. If you have code like this:

var task = new Task(async () =>
{
    A();
    await Task.Delay(1000);
    B();
});
task.Start();

that compiles down to using the Task constructor that takes an Action, with the async lambda becoming an async void. That Action will return the moment it awaits something that's not yet complete. So in my above example, that Task will complete the moment it hits that await.

I basically understood why it is executed this way. It would be good to add a constructor public Task(Func<Task> action){} to the Task class so that it would be possible to wait for the task to complete.

stephentoub commented 3 weeks ago

In general we discourage use of the Task constructors + Start. Their use is very limited. Task.Run already provides the desired overload; if you use Task.Run instead of the ctor + Start, it should "just work" the way you expect.

Mr0N commented 3 weeks ago

image The method Task.Run does not have these parameters.

stephentoub commented 3 weeks ago

The method Task.Run does not have these parameters.

If you need those options, you can use Task.Factory.StartNew and call .Unwrap on the result.

Mr0N commented 3 weeks ago

Well, plus everything is quite deceptive. For example, the Task.Run method accepts asynchronous lambdas. A user who uses the Task constructor also ideally thinks that it accepts asynchronous lambdas.

Mr0N commented 3 weeks ago

The Task class is essentially part of async/await.

stephentoub commented 3 weeks ago

A user who uses the Task constructor also ideally thinks that it accepts asynchronous lambdas.

I understand the concern. This is the danger of async void lambdas.