簡體   English   中英

await Task 和 await Func 的區別<task> ()</task>

[英]Difference between await Task and await Func<Task>()

我有一個程序需要並行運行多個異步任務。 兩個實現Task1()Task2()之間的主要區別:

Task1()保存FTask類型的Func<Task>並稍后等待Task.WhenAny(FTask())

Task2()保存由Task.Run()生成的Task並稍后等待Task.WhenAny(Task)

我認為它們應該是等效的,但是Task1()不能正常工作,但是Task2()可以。

該程序的流程是:如果空閑隊列中有可用的任務上下文,則將其出隊並使用異步任務對其進行設置; 當異步任務完成時,將上下文放回空閑隊列。

問題是保存Func<Task>Task1()不起作用,似乎異步任務有多個實例在運行。

完整的代碼是:

static async Task Task1()
{
    int qd = 4;
    var taskPool = Enumerable.Range(0, qd).Select(n => new TaskCtx()).ToArray();
    Queue<TaskCtx> qFree = new Queue<TaskCtx>(taskPool);
    Random r = new Random();

    long segs = 7, submit = 0;
    int token = 0;
    int ticket = 0;

    Console.WriteLine($"-------- Task1 start --------");
    
    while (submit < segs)
    {
        if (qFree.Count > 0)
        {
            var x = qFree.Dequeue();
            x.Token = token++;
            x.Delay1 = r.Next(10, 20);
            x.Delay2 = r.Next(30, 50);

            x.FTask = async () =>
            {
                Console.WriteLine($"[{x.Token}] start");
                await Task.Delay(x.Delay1);

                Console.WriteLine($"[{x.Token}] wait ticket: {ticket}");
                while (ticket != x.Token)
                {
                    await Task.Yield(); // yield and wait other task increases the ticket
                }
                
                Console.WriteLine($"[{x.Token}] acquire ticket: {ticket}");
                await Task.Delay(x.Delay2);
                Console.WriteLine($"[{x.Token}] release ticket: {ticket}");
                ticket++;
                
                qFree.Enqueue(x);
            };

            Console.WriteLine($"[{x.Token}] submit");
            submit++;
        }
        else await Task.WhenAny(taskPool.Select(x => x.FTask()));
    }

    await Task.WhenAll(taskPool.Select(x => x.FTask()));
    Console.WriteLine($"-------- Task1 finished --------");
}

static async Task Task2()
{
    int qd = 4;
    var taskPool = Enumerable.Range(0, qd).Select(n => new TaskCtx()).ToArray();
    Queue<TaskCtx> qFree = new Queue<TaskCtx>(taskPool);
    Random r = new Random();

    long segs = 7, submit = 0;
    int token = 0;
    int ticket = 0;

    Console.WriteLine($"-------- Task2 start --------");
    
    while (submit < segs)
    {
        if (qFree.Count > 0)
        {
            var x = qFree.Dequeue();
            x.Token = token++;
            x.Delay1 = r.Next(10, 20);
            x.Delay2 = r.Next(30, 50);

            x.Task = Task.Run(async () =>
            {
                Console.WriteLine($"[{x.Token}] start");
                await Task.Delay(x.Delay1);

                Console.WriteLine($"[{x.Token}] wait ticket: {ticket}");
                while (ticket != x.Token)
                {
                    await Task.Yield(); // yield and wait other task increases the ticket
                }
                
                Console.WriteLine($"[{x.Token}] acquire ticket: {ticket}");
                await Task.Delay(x.Delay2);
                Console.WriteLine($"[{x.Token}] release ticket: {ticket}");
                ticket++;
                
                qFree.Enqueue(x);
            });

            Console.WriteLine($"[{x.Token}] submit");
            submit++;
        }
        else await Task.WhenAny(taskPool.Select(x => x.Task));
    }

    await Task.WhenAll(taskPool.Select(x => x.Task));
    Console.WriteLine($"-------- Task2 finished --------");
}

public class TaskCtx
{
    public int Token;
    public int Delay1;
    public int Delay2;
    public Func<Task> FTask;
    public Task Task;

    public TaskCtx()
    {
        FTask = async () => await Task.CompletedTask;
        Task = Task.CompletedTask;
    }
}

Task1()的 output :

-------- Task1 start --------
[0] submit
[1] submit
[2] submit
[3] submit
[0] start
[1] start
[2] start
[3] start
[0] wait ticket: 0
[0] acquire ticket: 0
[2] wait ticket: 0
[1] wait ticket: 0
[3] wait ticket: 0
[0] release ticket: 0
[1] acquire ticket: 1   <-- [1] is running and got the ticket
[4] submit
[4] start
[1] start               <-- another [1] starts ???
[2] start
[3] start
[2] wait ticket: 1
[1] wait ticket: 1
[1] acquire ticket: 1
[4] wait ticket: 1
[3] wait ticket: 1
[1] release ticket: 1
[2] acquire ticket: 2
[2] acquire ticket: 2
[1] release ticket: 2
[3] acquire ticket: 3
[3] acquire ticket: 3
[5] submit
[6] submit
[4] start
[6] start
[2] start
[3] start
[2] wait ticket: 3
[4] wait ticket: 3
[6] wait ticket: 3
[3] wait ticket: 3
[3] acquire ticket: 3
[2] release ticket: 3
[4] acquire ticket: 4
[2] release ticket: 3
[4] acquire ticket: 4
[3] release ticket: 5
[6] acquire ticket: 6
[3] release ticket: 5
[3] release ticket: 7
[4] release ticket: 8
[4] release ticket: 8
[6] release ticket: 10

Task2()的 output :

-------- Task2 start --------
[0] submit
[1] submit
[2] submit
[3] submit
[0] start
[1] start
[2] start
[3] start
[2] wait ticket: 0
[1] wait ticket: 0
[3] wait ticket: 0
[0] wait ticket: 0
[0] acquire ticket: 0
[0] release ticket: 0
[1] acquire ticket: 1
[4] submit
[4] start
[4] wait ticket: 1
[1] release ticket: 1
[2] acquire ticket: 2
[5] submit
[5] start
[5] wait ticket: 2
[2] release ticket: 2
[3] acquire ticket: 3
[6] start
[6] submit
[6] wait ticket: 3
[3] release ticket: 3
[4] acquire ticket: 4
[4] release ticket: 4
[5] acquire ticket: 5
[5] release ticket: 5
[6] acquire ticket: 6
[6] release ticket: 6
-------- Task2 finished --------

更新:我做了另一個實現,使用普通的異步 function,而不是匿名的 function:

static async Task Task3()
{
    int qd = 4;
    var taskPool = Enumerable.Range(0, qd).Select(n => new TaskCtx()).ToArray();
    Queue<TaskCtx> qFree = new Queue<TaskCtx>(taskPool);
    Random r = new Random();

    long segs = 7, submit = 0;
    int token = 0;
    Ticket ticket = new Ticket(0);

    Console.WriteLine($"-------- Task3 start --------");
    
    while (submit < segs)
    {
        if (qFree.Count > 0)
        {
            var x = qFree.Dequeue();
            x.Token = token++;
            x.Delay1 = r.Next(10, 20);
            x.Delay2 = r.Next(30, 50);

            x.Task = RunTask3Async(x, ticket, qFree);

            Console.WriteLine($"[{x.Token}] submit");
            submit++;
        }
        else await Task.WhenAny(taskPool.Select(x => x.Task));
    }

    await Task.WhenAll(taskPool.Select(x => x.Task));
    Console.WriteLine($"-------- Task3 finished --------");
}

static async Task RunTask3Async(TaskCtx x, Ticket ticket, Queue<TaskCtx> qFree)
{
    Console.WriteLine($"[{x.Token}] start");
    await Task.Delay(x.Delay1);

    Console.WriteLine($"[{x.Token}] wait ticket: {ticket}");
    while (ticket.ticket != x.Token)
    {
        await Task.Yield(); // yield and wait other task increases the ticket
    }
    
    Console.WriteLine($"[{x.Token}] acquire ticket: {ticket}");
    await Task.Delay(x.Delay2);
    Console.WriteLine($"[{x.Token}] release ticket: {ticket}");
    ticket.ticket++;
    
    qFree.Enqueue(x);
}

public class Ticket
{
    public int ticket;

    public Ticket(int t)
    {
        ticket = t;
    }

    public override string ToString()
    {
        return $"{ticket}";
    }
}

Task3()也有效,output 是:

-------- Task3 start --------
[0] start
[0] submit
[1] start
[1] submit
[2] start
[2] submit
[3] start
[3] submit
[0] wait ticket: 0
[3] wait ticket: 0
[0] acquire ticket: 0
[2] wait ticket: 0
[1] wait ticket: 0
[0] release ticket: 0
[1] acquire ticket: 1
[4] start
[4] submit
[4] wait ticket: 1
[1] release ticket: 1
[2] acquire ticket: 2
[5] start
[5] submit
[5] wait ticket: 2
[2] release ticket: 2
[3] acquire ticket: 3
[6] start
[6] submit
[6] wait ticket: 3
[3] release ticket: 3
[4] acquire ticket: 4
[4] release ticket: 4
[5] acquire ticket: 5
[5] release ticket: 5
[6] acquire ticket: 6
[6] release ticket: 6
-------- Task3 finished --------

UPDATE2:我試圖最小化這個例子,但下面的代碼沒有這個問題。

var FTasks = Enumerable.Range(0, 8).Select(i => async () =>
{
    Console.WriteLine($"[{i}] start");
    await Task.Delay(i);
    Console.WriteLine($"[{i}] end");
}).ToArray();

await Task.WhenAll(FTasks.Select(x => x()));

Console.WriteLine("----------------");

var Tasks = Enumerable.Range(0, 8).Select(i => Task.Run(async () =>
{
    Console.WriteLine($"[{i}] start");
    await Task.Delay(i);
    Console.WriteLine($"[{i}] end");
})).ToArray();

await Task.WhenAll(Tasks);

正如其他人指出的那樣, TaskFunc<Task>之間的主要區別在於第一個代表一個操作(已經開始),第二個代表一個開始操作的委托。 這與任何TFunc<T>的邏輯相同。

那么,“為什么這些運行不止一次?”的問題。 通過查看您的Task1來回答:

        else await Task.WhenAny(taskPool.Select(x => x.FTask()));
    }

    await Task.WhenAll(taskPool.Select(x => x.FTask()));

每次您的代碼調用FTask()時,它都會開始一個的異步操作。 所以每次調用Task.WhenAny都會啟動taskPool中的所有任務,最后一次調用Task.WhenAll會再次啟動taskPool中的所有任務。

Task.Run(...)存儲在 Task 和將其分配給 Func 之間的主要區別在於執行的時間。 Task.Run()直接開始工作,而函數是惰性的,即在調用函數時開始工作。

這個問題就像斯蒂芬在他的回答中所說的那樣,每次等待異步委托時,都會創建一個新的異步任務實例並從頭開始。

這是說明此問題的最小示例:

int i = 0;
var ftask = async () =>
{
    Console.WriteLine($"[{i++}] Func<Task>");
    await Task.Yield();
};

await ftask();
await ftask();

//--------------------

int j = 0;
var task = Task.Run(async () =>
{
    Console.WriteLine($"[{j++}] Task.Run()");
    await Task.Yield();
});

await task;
await task;

output:

[0] Func<Task>
[1] Func<Task>
[0] Task.Run()

這個問題本質上歸結為調用之間的區別

public Task MyMethod();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM