在Run
中,我正在尝试将10,000个任务排队。 (这只是一个实验,所以我可以更好地理解async await
。)但是第一个循环需要三秒钟才能完成。我希望这样做会更快,因为我只是尝试将任务排队,然后在几行之后await
。 Run
仍在alreadyCompleted
中,因为任务似乎已同步运行。最后,仍然在Run
内,第二个循环需要1毫秒才能完成,再次显示任务已经运行。
如何在我允许使用Run
之前通过await
方法将要运行的任务排队的同时?我会了解我的某些任务是否在alreadyCompleted
被检查时已完成,但是似乎所有任务都已完成很奇怪。在ASP.NET Core 3,.NET Core 3控制台和.NET Framework控制台之间,此行为是一致的。谢谢。
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public static class SqlUtility
{
public static async Task Run()
{
const int max = 10000;
Stopwatch sw1 = Stopwatch.StartNew();
Task<int>[] tasks = new Task<int>[max];
for(int i=0;i<max;i++)
{
tasks[i] = GetVideoID();
}
sw1.Stop();//ElapsedMilliseconds: 3169
bool alreadyCompleted = tasks.All(t => t.IsCompletedSuccessfully);//true
Stopwatch sw2 = Stopwatch.StartNew();
for (int i = 0; i < max; i++)
{
await tasks[i].ConfigureAwait(false);
}
sw2.Stop();//ElapsedMilliseconds: 1
}
public static async Task<int> GetVideoID()
{
const string connectionString =
"Server=localhost;Database=[Redacted];Integrated Security=true";
SqlParameter[] parameters = new SqlParameter[]
{
new SqlParameter("VideoID", SqlDbType.Int) { Value = 1000 }
};
const string commandText = "select * from Video where VideoID=@VideoID";
IAsyncEnumerable<object> values = GetValuesAsync(connectionString, parameters, commandText,
CancellationToken.None);
object videoID = await values.FirstAsync().ConfigureAwait(false);//1000
return (int)videoID;
}
public static async IAsyncEnumerable<object> GetValuesAsync(
string connectionString, SqlParameter[] parameters, string commandText,
[EnumeratorCancellation]CancellationToken cancellationToken)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
using (SqlCommand command = new SqlCommand(commandText, connection))
{
command.Parameters.AddRange(parameters);
using (var reader = await command.ExecuteReaderAsync()
.ConfigureAwait(false))
{
while (await reader.ReadAsync().ConfigureAwait(false))
{
yield return reader[0];
}
}
}
}
}
}
运行.NET Core应用程序并使用max
为1,000时,alreadyCompleted
为假。实际上,查看每个任务的IsCompleted
,当前alreadyCompleted
所在的位置都没有完成任务。这就是我原本会发生的事情。
但是,如果max
设置为10,000(与原来一样),则alreadyCompleted
就是假的,就像上面一样。有趣的是,检查tasks[max - 1].IsCompleted
会产生false
(首先检查最后一个任务)。 tasks.All(t => t.IsCompletedSuccessfully)
最后检查最后一个结果,此时IsCompleted
是true
。
答案 0 :(得分:4)
了解async
方法开始同步运行很重要。当await
被赋予不完整 Task
时,魔术就发生了。 (如果给出的是完成 Task
,则执行将继续同步)
因此,当您致电GetVideoID
时,它就是这样做的:
GetVideoID
一直运行到GetValuesAsync()
GetValuesAsync
一直运行到await connection.OpenAsync
OpenAsync
一直运行到returns a Task
。await
中的GetValuesAsync
看到不完整的Task
,并返回自己的不完整的Task
。GetVideoID
,直到调用records.FirstAsync()
。FirstAsync()
运行直到返回不完整的Task
。await
中的GetVideoID
看到不完整的Task
,并返回自己的不完整的Task
。Run
。所有这些都是同步发生的。花三秒钟完成10,000次是很合理的。
为什么检查每个Task
时已经完成?简短的答案是这些任务的延续正在其他线程上运行。
如果这不是一个Web应用程序(或者它是.NET Framework),那么ConfigureAwait(false)
就会使这种情况发生。否则,继续操作必须等到Run()
的执行暂停。但是使用ConfigureAwait(false)
,这些延续可以在任何上下文中运行,因此它们可以在ThreadPool线程上运行。
如果这是{。{3}}的ASP.NET Core,即使没有ConfigureAwait(false)
,也还是会发生。该文章在标题does not have any synchronization context下对此进行了讨论:
ASP.NET Core没有
SynchronizationContext
,因此await
默认为线程池上下文。因此,在ASP.NET Core世界中,异步延续可以在任何线程上运行,并且它们都可以在 parallel 中运行。
总结:
更新:如果要避免最初的3秒延迟,则可以使用Task.Run
在另一个线程上启动任务,这将使您的方法几乎可以继续执行立即。只需将其替换:
tasks[i] = GetVideoID();
与此:
tasks[i] = Task.Run(() => GetVideoID());
但是,这样做有开销,所以您应该花时间完成整个操作需要多长时间,看看它是否对您有帮助。
尽管由于可用线程数量有限,在ASP.NET中这可能不是一个好主意。