2

Assume I created a library containing such method:

Task MyLibraryMethodAsync()
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    Action myWorkItem =
        () =>
        {
            // Simulate some work.
            // Actual work items depend on input params.
            Thread.Sleep(TimeSpan.FromSeconds(1));

            taskCompletionSource.SetResult(null);
        };

    // The next two lines is simplification for demonstration.
    // I do not have access to the workerThread - it is created
    // and managed for me by another lib.
    // All I can do - is to post some short work items to it.
    var workerThread = new Thread(new ThreadStart(myWorkItem));
    workerThread.Start();

    return taskCompletionSource.Task;
}

Any user of my lib can call MyLibraryMethodAsync like this

await MyLibraryMethodAsync().ConfigureAwait(false);
VeryLongRunningMethod();
void VeryLongRunningMethod()
{
    Thread.Sleep(TimeSpan.FromHours(1));
}

And here the problem comes – VeryLongRunningMethod will be executed inside the taskCompletionSource.SetResult(null) call and thus it will block workerThread for a long period of time, which is not desired behavior because workerThread is intended to run small portions of code (work items).

How do I substitute context / scheduler to a thread pool inside the returned task making await x.ConfigureAwait(false) to continue on the thread pool, but not on the workerThread?

The current solution I found is

Task MyLibraryMethodAsync()
{
    // ...

    return taskCompletionSource.Task
        .ContinueWith(x => x.Result, TaskScheduler.Default);
}

However, I do not like it due to overhead it creates. May be more elegant solution exists?

A-student
  • 785
  • 1
  • 5
  • 12

2 Answers2

4

As of .NET 4.6 there is an option in TaskCreationOptions called RunContinuationsAsynchronously, which does exactly what you want, it ensures that all continuations are run asynchronously, rather than run synchronously when setting the result. TaskCompletionSource has an optional TaskCreationOption parameter in its constructor for you to provide that option.

If you're using an earlier version of .NET you'll need to do a less efficient hack, such as adding another continuation, as you showed, or explicitly setting the result in a thread pool thread, rather than through the callback action.

Servy
  • 202,030
  • 26
  • 332
  • 449
0

Not sure if I understand you correctly, but you can explicitly create a background task to avoid blocking:

await MyLibraryMethodAsync().ConfigureAwait(false);
await Task.Run(() => VeryLongRunningMethod());

You can even leave out the ConfigureAwait then:

await MyLibraryMethodAsync()
await Task.Run(() => VeryLongRunningMethod());

EDIT: Based on your comment: If you as the author of the library want to prevent blocking the Thread, you can use:

Task.Run(() => taskCompletionSource.SetResult(null));
Matthias
  • 12,053
  • 4
  • 49
  • 91
  • From the user point of view, you are right. But I’m lib developer and I need to make sure that lib users won’t block the worker thread anyhow. – A-student Jan 07 '16 at 15:07
  • 2
    See EDIT - what about using Task.Run(() => taskCompletionSource.SetResult(null)); to set the result? – Matthias Jan 07 '16 at 15:11
  • This gives the same overhead as my current solution. Anyway, Servy solved my problem. – A-student Jan 07 '16 at 17:42