5
votes

I need some help with TPL and Tasks

Here are my scenarios:

  1. I have a few event handlers that spawn tasks. If an event is invoked before its previous task is completed, I want to wait (block) it before proceeding.

  2. I have a synchronous method that, when invoked, must wait until any spawned tasks from any event handlers are done, before proceeding.


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 */ });
}

An actual scenario would be:

  • OnFetchData() => Spawns task. All subsequent calls to this needs to be queued.
  • OnSyncSettings() => Spawns task. All subsequent calls to this needs to be queued.

  • OnAutoBackup() => Synchronous method. Wait for any other tasks (e.g, Fetch data/Sync settings) to complete before saving.

  • OnFullBackup() => Synchronous method. Manually runs FetchData() and SyncSettings(), waits for completion, proceed.

My question is simply: How can I do this?

Would this approach be correct?

  1. Each event handler remembers its last . When invoked, it'll wait for all tasks in its list to complete before proceeding.

  2. For the synchronous method, it needs to wait for all tasks (From every event handler) to


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

}

Thanks!


[Edit]

When DoSomething() is waiting and Event1 is raised? What if DoSomething() is running and an event is raised?

OnEvents()

  • If first task: Spawns task and execute asynchronously
  • If previous task still running, queue/block. Critical section.
  • Assume:
    • These events typically not raised multiple times.
    • The UI is expected to prevent user from raising the same event while still running.

DoSomething() (when called)

  • Assume: Event notifications are disabled.
  • Assume: No new events will be queued.
  • Expected: to wait until all remaining queued tasks are completed before proceeding
  • Expected: to be a synchronous call, and does not return to caller until executed through
2
If it's an option, go .net 4.5 to make use of async/await goodness. It simplifies this kind of code considerably.spender
@spender, thanks for the reply. Unfortunately, 4.5 is not an option for my current project. :(user1705081
What should happen when DoSomething() is waiting and Event1 is raised? Should the event code wait until DoSomething() is complete? Or should DoSomething() wait for the new event too? What if DoSomething() is running and an event is raised?svick
@svick I've updated my question with further assumptions/expected behaviouruser1705081
async/await is now available in .NET 4, Silverlight 4, Silverlight 5 and Windows phone 7.1. Search NuGet for Microsoft.Bcl.AsyncGreg Woods

2 Answers

3
votes

The approach you describe has concurrency issues. For example, right after the null check on _lastEvent1Task, some other thread may set _lastEvent1Task to null, causing a NullReferenceException.

I'm assuming that every task should run synchronously but some should not be blocking. The best way to achieve this is by using a TaskScheduler that utilizes just a single thread, you can find an example here.

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

For the non-blocking methods you then get something like this:

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

For the blocking methods you get:

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

When you schedule tasks, the task scheduler will use just one thread for scheduling them, guaranteeing that all tasks run synchronously.

0
votes

I think you need to use the task schedulers from the ParExtSamples. You will typically need the OrderedTaskScheduler for your OnEvents() and then wait on all those scheduled tasks to complete in the DoSomething() method, or simply schedule your DoSomething() method on the same OrderedTaskScheduler and it will only be executed once tasks, scheduled before it, are complete.