1

一个非常简单的任务:

Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
}).ContinueWith(_ =>
{
    lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());

它在运行时按预期执行,Form_Load()但在从与 BindingSource 相关的任何事件运行时会阻塞 5 秒。

我错过了一些关于 BindingSources 的东西吗?我正在使用.NET 4。

4

2 回答 2

1

您的代码将 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。

于 2012-06-27T14:12:05.827 回答
0

我通过将默认任务调度程序分配给任务来解决这个问题。最终形式变为:

Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).ContinueWith(_ =>
{
    lblStatus.Text = "Done";
}, TaskScheduler.FromCurrentSynchronizationContext());

我不是 C# 方面的专家,所以我真的不知道它为什么会这样工作。

于 2012-06-26T17:46:41.147 回答