如何排队/等待TPL任务完成

5

我需要一些关于TPL和Tasks的帮助。

这是我的场景:

  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
@spender,感谢您的回复。不幸的是,4.5对于我的当前项目来说不是一个选项。:( - user1705081
1
DoSomething()正在等待并且Event1被触发时,应该发生什么?事件代码是否应该等待DoSomething()完成?还是DoSomething()也应该等待新事件?如果DoSomething()正在运行并且事件被触发会怎样? - svick
@svick 我已更新我的问题,附加了进一步的假设/期望行为。 - user1705081
异步/等待现在可在.NET 4、Silverlight 4、Silverlight 5和Windows Phone 7.1中使用。在NuGet中搜索Microsoft.Bcl.Async。 - Greg Woods
2个回答

3
你所描述的方法存在并发问题。例如,在对_lastEvent1Task进行空值检查之后,其他线程可能会将_lastEvent1Task设置为null,从而导致NullReferenceException
我假设每个任务都应该同步运行,但有些任务不应该阻塞。实现这一点的最佳方法是使用一个TaskScheduler,它只利用单个线程,你可以在这里找到一个示例。
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
svick是正确的。我希望不同的事件有它们自己的单一任务/线程。然而,当调用同步方法(DoSomething())时,我希望它在继续之前等待所有正在运行的任务完成。我开始觉得我应该为每个事件保留一个任务,并使用Task.WaitAll()来实现。但是自定义任务调度器的想法也很有趣。我会仔细考虑这个问题。 - user1705081

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接