您的代码将 ThreadPool 线程阻塞 5 秒。如果您为大量事件执行此代码,您可能会耗尽所有线程池线程并有效地阻塞您的应用程序,直到所有 Sleep 语句完成。
两个代码示例都使用默认线程调度程序执行。不同之处在于第二个示例使用 TaskCreationOptions.LongRunning 指示调度程序创建新线程而不是等待池线程。这可能会克服最初的问题,但它仍然不是正确的解决方案,因为您仍在浪费线程并且可能没有线程可用于其他任务。
一个正确的实现是使用一个TaskSource,它会在定时器到期时发出信号。这样你就不会阻塞任何线程。
C# 5 已经通过Task.Delay方法支持这一点。如果您使用Visual Studio 2012的异步目标包或2010的Async v3 CTP ,则可以在 .NET 4.0 中使用它
您还可以在 ParallelExtensionExtras 库中找到类似的方法。TaskFactory.StartNewDelayed扩展方法的工作方式几乎相同。Stephen Toub 文章中的示例代码提供了一个简化版本:
public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
// Validate arguments
if (millisecondsDelay < 0)
throw new ArgumentOutOfRangeException("millisecondsDelay");
if (action == null) throw new ArgumentNullException("action");
// Create a trigger used to start the task
var tcs = new TaskCompletionSource<object>();
// Start a timer that will trigger it
var timer = new Timer(
_ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);
// Create and return a task that will be scheduled when the trigger fires.
return tcs.Task.ContinueWith(_ =>
{
timer.Dispose();
action();
});
}
使用 ParallelExtensionsExtras 中的版本,您可以重写代码,如下所示:
Task.Factory.StartNewDelayed(5000).ContinueWith(_ =>
{
lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());
编辑:
看来实际代码毕竟没有 Thread.Sleep 。它执行一些繁重的数据库相关操作。不过效果是一样的。在每个 BindingSource 事件之后启动一个新任务可能会导致线程池耗尽的运行任务过多。
一种解决方案是再次使用带有TaskFactory.StartNew(Action,TaskCreationOptions)覆盖的 TaskCreationOptions.LongRunning 标志来指示调度程序创建更多线程。
更好的解决方案是异步执行数据库操作,使用 BeginExecuteXXX,EndExecuteXXX 方法结合TaskFactory.FromAsync将异步调用转换为任务。这样数据库操作根本不会阻塞任何线程。
你可以这样写:
Task<SqlDataReader> task = Task<SqlDataReader>.Factory.FromAsync(
cmd.BeginExecuteReader(CommandBehavior.CloseConnection),
cmd.EndExecuteReader)
.ContinueWith(reader=>
{
//do some processing
reader.Close();
});
.ContinueWith(_ =>
{
lblStatus.Text="Done";
},TaskScheduler.FromCurrentSynchronizationContext());
异步读取和处理数据,并在处理完成后更新 UI。