[英]Difference between Func<T, Task> and anonymous async await Action<T>
[英]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);
正如其他人指出的那樣, Task
和Func<Task>
之間的主要區別在於第一個代表一個操作(已經開始),第二個代表一個開始操作的委托。 這與任何T
和Func<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.