await Task 和 await Func<Task>() 的区别
Difference between await Task and await Func<Task>()
我有一个程序需要 运行 并行执行多个异步任务。 Task1()
和 Task2()
两个工具的主要区别:
Task1()
保存类型 Func<Task>
的 FTask
并稍后等待 Task.WhenAny(FTask())
,
Task2()
保存由 Task.Run()
生成的 Task
并稍后等待 Task.WhenAny(Task)
.
我认为它们应该是等价的,但是 Task1()
不能正常工作,但是 Task2()
可以。
这个程序的流程是:
如果空闲队列中有可用的任务上下文,则将其出列并使用异步任务对其进行设置;异步任务完成后将上下文放回空闲队列。
问题是 Task1()
保存 Func<Task>
不起作用,似乎异步任务有多个实例 运行ning.
完整代码为:
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()
的输出:
-------- 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()
的输出:
-------- 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 --------
更新:
我做了另一个实现,使用普通的异步函数,而不是匿名函数:
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()
也有效,输出为:
-------- 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 --------
更新2:
我尽量减少示例,但下面的代码没有这个问题。
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);
这个问题本质上归结为调用
的区别
public Task MyMethod();
正在打电话
public void MyMethod()
使用Task.Run
。本质区别在于第二个示例将在后台线程上显式 运行 。第一个示例将在当前线程上 运行 直到第一个等待。此时它可能会继续,也可能会在稍后安排 'continuation'。此延续将 运行 在与调用线程相同的同步上下文中,这意味着在 UI 程序中,如果从 [=] 调用,它将 运行 在 UI 线程上23=]线程。
对于像您这样的控制台程序,实际差别不大,因为第一个操作是 Task.Delay
,而由于您没有 UI,它将 运行 线程池线程上的延续。
将 Task.Run(...)
存储在任务中和将其分配给 Func 之间的主要区别在于执行的时间。 Task.Run()
直接开始工作而函数是惰性的,即调用函数时开始工作。
正如其他人所指出的,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()
时,它都会启动一个 new 异步操作。所以 每次 调用 Task.WhenAny
将启动 taskPool
中的所有任务,最后调用 Task.WhenAll
将启动 [=] 中的所有任务18=] 再次。
这个问题就像一样,每次等待异步委托时,都会创建一个新的异步任务实例并从头开始。
这是一个说明此问题的最小示例:
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;
输出:
[0] Func<Task>
[1] Func<Task>
[0] Task.Run()
我有一个程序需要 运行 并行执行多个异步任务。 Task1()
和 Task2()
两个工具的主要区别:
Task1()
保存类型 Func<Task>
的 FTask
并稍后等待 Task.WhenAny(FTask())
,
Task2()
保存由 Task.Run()
生成的 Task
并稍后等待 Task.WhenAny(Task)
.
我认为它们应该是等价的,但是 Task1()
不能正常工作,但是 Task2()
可以。
这个程序的流程是: 如果空闲队列中有可用的任务上下文,则将其出列并使用异步任务对其进行设置;异步任务完成后将上下文放回空闲队列。
问题是 Task1()
保存 Func<Task>
不起作用,似乎异步任务有多个实例 运行ning.
完整代码为:
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()
的输出:
-------- 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()
的输出:
-------- 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 --------
更新: 我做了另一个实现,使用普通的异步函数,而不是匿名函数:
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()
也有效,输出为:
-------- 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 --------
更新2: 我尽量减少示例,但下面的代码没有这个问题。
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);
这个问题本质上归结为调用
的区别public Task MyMethod();
正在打电话
public void MyMethod()
使用Task.Run
。本质区别在于第二个示例将在后台线程上显式 运行 。第一个示例将在当前线程上 运行 直到第一个等待。此时它可能会继续,也可能会在稍后安排 'continuation'。此延续将 运行 在与调用线程相同的同步上下文中,这意味着在 UI 程序中,如果从 [=] 调用,它将 运行 在 UI 线程上23=]线程。
对于像您这样的控制台程序,实际差别不大,因为第一个操作是 Task.Delay
,而由于您没有 UI,它将 运行 线程池线程上的延续。
将 Task.Run(...)
存储在任务中和将其分配给 Func 之间的主要区别在于执行的时间。 Task.Run()
直接开始工作而函数是惰性的,即调用函数时开始工作。
正如其他人所指出的,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()
时,它都会启动一个 new 异步操作。所以 每次 调用 Task.WhenAny
将启动 taskPool
中的所有任务,最后调用 Task.WhenAll
将启动 [=] 中的所有任务18=] 再次。
这个问题就像
这是一个说明此问题的最小示例:
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;
输出:
[0] Func<Task>
[1] Func<Task>
[0] Task.Run()