【问题标题】:How to queue/wait for TPL tasks to complete如何排队/等待 TPL 任务完成
【发布时间】:2012-10-09 00:53:03
【问题描述】:

我需要一些有关 TPL 和任务的帮助

这是我的场景:

  1. 我有一些产生任务的事件处理程序。如果一个事件在其上一个任务完成之前被调用,我想在继续之前等待(阻止)它。

  2. 我有一个同步方法,在调用该方法时,必须等到任何事件处理程序的任何派生任务完成后,才能继续。


public void DoSomething()
{
    // Expects any running tasks from OnEvent1(), OnEvent2(), OnEvent3()
    // to be completed before proceeding.
}

public void OnEvent1()
{
    Task.Factory
    .StartNew(()=>{ /*Long running task*/ })
    .ContinueWith(task=>{ /* Updates UI */ });
}

public void OnEvent2()
{
    Task.Factory
    .StartNew(()=>{ /*Long running task*/ })
    .ContinueWith(task=>{ /* Updates UI */ });
}

public void OnEvent3()
{
    Task.Factory
    .StartNew(()=>{ /*Long running task*/ })
    .ContinueWith(task=>{ /* Updates UI */ });
}

实际情况是:

  • OnFetchData() => 生成任务。对此的所有后续调用都需要排队。
  • OnSyncSettings() => 生成任务。对此的所有后续调用都需要排队。

  • OnAutoBackup() => 同步方法。等待任何其他任务(例如,获取数据/同步设置)完成后再保存。

  • OnFullBackup() => 同步方法。手动运行 FetchData() 和 SyncSettings(),等待完成,继续。

我的问题很简单:我该怎么做?

这种方法正确吗?

  1. 每个事件处理程序都会记住它的最后一个 .调用时,它将等待其列表中的所有任务完成后再继续。

  2. 对于同步方法,它需要等待所有任务(从每个事件处理程序)到


Task _lastEvent1Task;
public void OnEvent1()
{
    // Wait for all tasks to complete before proceeding
    if (_lastEvent1Task!=null)
    _lastEvent1Task.Clear();

    // Spawns new task
    _lastEvent1Task = Task.Factory.StartNew(()=>{ });
}

public void OnEvent3()
{
    // Wait for any running tasks to complete before proceeding
    if (_lastEvent1Task != null) _lastEvent1Task.Wait();
    if (_lastEvent2Task != null) _lastEvent2Task.Wait();
    ...
    // Proceed

}

谢谢!


[编辑]

当 DoSomething() 正在等待并引发 Event1 时? 如果 DoSomething() 正在运行并引发事件怎么办?

OnEvents()

  • 如果是第一个任务:生成任务并异步执行
  • 如果之前的任务仍在运行,则排队/阻塞。关键部分。
  • 假设:
    • 这些事件通常不会多次引发。
    • UI 应防止用户在运行时引发相同的事件。

DoSomething()(调用时)

  • 假设:事件通知被禁用。
  • 假设:不会有新事件排队。
  • 预期:等到所有剩余的排队任务完成后再继续
  • 预期:是同步调用,直到通过
  • 执行才返回调用者

【问题讨论】:

  • 如果可以选择,请转到 .net 4.5 以利用 async/await 的优点。它大大简化了这种代码。
  • @spender,感谢您的回复。不幸的是,4.5 不是我当前项目的选项。 :(
  • DoSomething() 正在等待并引发Event1 时应该发生什么?事件代码是否应该等到DoSomething() 完成?还是应该DoSomething() 也等待新事件?如果DoSomething() 正在运行并引发事件怎么办?
  • @svick 我已经用进一步的假设/预期行为更新了我的问题
  • async/await 现在可用于 .NET 4、Silverlight 4、Silverlight 5 和 Windows phone 7.1。在 NuGet 中搜索 Microsoft.Bcl.Async

标签: c# .net concurrency task-parallel-library


【解决方案1】:

您描述的方法存在并发问题。例如,在对_lastEvent1Task 进行空检查之后,其他一些线程可能会将_lastEvent1Task 设置为空,从而导致NullReferenceException

我假设每个任务都应该同步运行,但有些任务不应该阻塞。实现这一点的最佳方法是使用仅使用单个线程的TaskScheduler,您可以找到一个示例here

var scheduler = new SingleThreadedTaskScheduler();
var taskFactory = new TaskFactory(scheduler);

对于非阻塞方法,您会得到如下内容:

public void NonBlockingMethod()
{
    taskFactory
        .StartNew(() => { /* Long-running work. */ })
        .ContinueWith(t => { /* Update UI. */ });
}

对于你得到的阻塞方法:

public void BlockingMethod()
{
    taskFactory
        .StartNew(() => { /* Do work. */ })
        .Wait();
}

当你调度任务时,任务调度器将只使用一个线程来调度它们,保证所有任务同步运行。

【讨论】:

  • 但这意味着来自不同事件的工作不会同时运行,我认为 OP 想要这样(尽管它完全清楚)。
  • svick 是正确的。我希望不同的事件有自己的 single 任务/线程。但是,当调用同步方法 (DoSomething()) 时,我希望它等待所有正在运行的任务完成,然后再继续。我开始认为我应该为每个事件保留一个任务,并改为执行 Task.WaitAll()。但是自定义任务调度器的想法也很有趣。会考虑一下
【解决方案2】:

我认为您需要使用ParExtSamples 中的任务调度程序。 您的 OnEvents() 通常需要 OrderedTaskScheduler,然后在 DoSomething() 方法中等待所有这些计划任务完成,或者只需将您的 DoSomething() 方法安排在同一个 OrderedTaskScheduler 上,它只会在之前安排的任务完成后执行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多