1

I declared a method which should run a Task asynchronously, then, when the Task finishes, run the specified delegate synchronously.

C# example:

private void WaitUntilLoadedAsync(Process p, Action callback, int timeout = 1500)
{
    Task.Factory.StartNew(() => ProcessUtil.WaitUntilLoaded(p, timeout)).
                 ContinueWith(() => callback.Invoke()).RunSynchronously();

}

VB.Net Example:

Private Sub WaitUntilLoadedAsync(ByVal p As Process,
                                 ByVal callback As Action,
                                 Optional ByVal timeout As Integer = 1500)

    Task.Factory.StartNew(Sub() ProcessUtil.WaitUntilLoaded(p, timeout)).
                 ContinueWith(Sub() callback.Invoke()).RunSynchronously()

End Sub

However, the RunSynchronously method throws a System.InvalidOperationException exception teling me that a continuation Task cannot be ran synchronouslly.

Then, how I could do it?.

Note(s):

  • I'm able to support Async/Await solutions.

  • I will preserve the "tiny" logic or cyclomatic complexity of my method, I mean, it is just a method on which I pass an Acton, no delegates are declared outside the method or other complex things that could cause the end-user to write more than an Action. The method itself should automate all the required operations to perform that continuation task synchronouslly, no complexity outside.

ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • When you say "synchronously", do you mean related to the first task? Or related to the `WaitUntilLoadedAsync` method? – Yacoub Massad Nov 04 '15 at 01:32
  • @Yacoub Massad Thanks for comment. In the code block are two Tasks, the first task calls the **ProcessUtil.WaitUntilLoaded** method, and the second task (continuation task) invokes the specified **Action**. I will invoke that **Action** synchronouslly, when the first task has finished asynchronously. – ElektroStudios Nov 04 '15 at 01:34
  • Yes, but what does that mean in this context? Continuation tasks will only run after the original task is done. So if you want it to be synchronous related to the first task, then you already have it without the `RunSynchronously` invocation. – Yacoub Massad Nov 04 '15 at 01:35
  • Do you want `WaitUntilLoadedAsync` to return control to the caller before `callback.Invoke()` is invoked? or after it is invoked? – Yacoub Massad Nov 04 '15 at 01:39
  • @Yacoub Massad In other words I want the continuation task to be synchronouslly related to the first task, but not asynchronouslly related to the caller thread. I will avoid that the continuation task runs on a different thread. – ElektroStudios Nov 04 '15 at 01:39
  • @Yacoub Massad yes! before, I think so. To launch it in the caller thread. – ElektroStudios Nov 04 '15 at 01:41
  • What do you mean yes? I asked two questions. Which is the correct one? – Yacoub Massad Nov 04 '15 at 01:42
  • So you want `callback.Invoke()` to be invoked on the thread that invoked `WaitUntilLoadedAsync`? Is the thread that invoked `WaitUntilLoadedAsync` a UI thread? – Yacoub Massad Nov 04 '15 at 01:42
  • @Yacoub Massad Yes in this sceneario it is the main UI thread. I would like to invoke the continuation task (**callback.Invoke()**) on the same thread to avoid messing with **xxx.InvokeRequired** inside the code block that reffers the **Action** I pass to the method, just for the ease of coding, If I could avoid invoke it on a different thread then why don't?. (sorry for my english) – ElektroStudios Nov 04 '15 at 01:44
  • @Yacoub Massad I found that the `ContinueWith` method has an overload that takes as parameter this enum value `TaskContinuationOptions.ExecuteSynchronously` which at first view reading its descrption seems to say exactly what I want to do, however, I'm not sure how to properlly assign the next needed parameter, `TaskScheduler` and throws an exception because I assigned it as **Null**. – ElektroStudios Nov 04 '15 at 01:52
  • 1
    "I'm able to support Async/Await solutions" - just `await` first task than... Learning intricacies of lower level methods is nice, but for practical code `await` should be perfectly enough. – Alexei Levenkov Nov 04 '15 at 02:08

2 Answers2

2

Sounds like you are looking for TaskContinuationsOptions.ExecuteSynchronously

Per MSDN:

Specifies that the continuation task should be executed synchronously. With this option specified, the continuation runs on the same thread that causes the antecedent task to transition into its final state. If the antecedent is already complete when the continuation is created, the continuation will run on the thread that creates the continuation. If the antecedent's CancellationTokenSource is disposed in a finally block (Finally in Visual Basic), a continuation with this option will run in that finally block. Only very short-running continuations should be executed synchronously.

Because the task executes synchronously, there is no need to call a method such as Task.Wait to ensure that the calling thread waits for the task to complete.

To use it just pass it as a flag using the overload .ContinueWith(Action<task>, TaskContinuationOptions)

However this won't be synchronous if the thread where the parent executed is aborted (which is uncommon).

Community
  • 1
  • 1
Alfredo MS
  • 605
  • 6
  • 8
  • Thankyou so much, but I discovered that overload does not cause the continuation task be ran on the caller of **WaitUntilLoadedAsync**, in difference of the overload shown by @Yacoub Massad – ElektroStudios Nov 04 '15 at 02:05
2

So, as I understand from the question and the comments, you want to run Task 1 on a thread-pool thread (background thread) and you want to run Task 2 (which is a continuation of Task 1) on the UI thread.

Here is how you can do it:

private void WaitUntilLoadedAsync(Process p, Action callback, int timeout = 1500)
{
    var thread_scheduler = TaskScheduler.FromCurrentSynchronizationContext();

    Task.Factory.StartNew(() => ProcessUtil.WaitUntilLoaded(p, timeout)).
        ContinueWith(t => callback.Invoke(), CancellationToken.None, TaskContinuationOptions.None,
            thread_scheduler);
}

What this code is doing is that it asks the TPL to run the continuation task on a scheduler that will execute tasks in the UI thread.

This assumes that WaitUntilLoadedAsync is called from the UI thread. If this is not the case, simply make thread_schedular an instance variable (or in some scope that is reachable from this method), and make sure that you initialize it from the UI thread.

Please note that the control will return to the caller of WaitUntilLoadedAsync immediately, and the callback will be executed on the UI thread after Task 1 is finished.

If you want to execute the continuation task on the UI thread only if WaitUntilLoadedAsync is invoked from a UI thread (and execute it on a thread-pool thread otherwise), then define thread_scheduler as the following:

var thread_scheduler = SynchronizationContext.Current == null
    ? TaskScheduler.Default
    : TaskScheduler.FromCurrentSynchronizationContext();
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • related to your solution, to make things safe as possible, could you please confirm that this conditional will ensure that method logic will be safe to avoid a `InvalidOperationException` if I try to run it from a non-UI thread? `if (System.Windows.Forms.Application.MessageLoop) { tScheduler = TaskScheduler.FromCurrentSynchronizationContext(); } else { tScheduler = TaskScheduler.Current; }` – ElektroStudios Nov 04 '15 at 02:18
  • Is your application a UI application? e.g. is it a windows forms application? Do you always want the continuation task to run on the UI thread? or whatever thread that invoked `WaitUntilLoadedAsync`? – Yacoub Massad Nov 04 '15 at 02:20
  • It is a UI application based on WinForms technology, but I pretend to add that method on a library (dll assembly) that could be executed "everywhere", I want the continuation task to run on the UI thread only if **WaitUntilLoadedAsync** was launched from an UI thread, is this possible with that conditional?. By the moment I'm not having issueas with that conditional when launching the method from a UI or non-UI thread, but I will ensure. – ElektroStudios Nov 04 '15 at 02:22
  • Its ok :), thankyou so much for sharing your knowledges about multi-threading programming. – ElektroStudios Nov 04 '15 at 02:30
  • 1
    Take a look also at [this question](http://stackoverflow.com/questions/6800705/why-is-taskscheduler-current-the-default-taskscheduler) – Yacoub Massad Nov 04 '15 at 02:31