How can I perform wait without blocking thread and without TPL' ContinueWith? For ex. in 3.5? I know about TPL's ported version for 3.5 (by RX team), but i'm curious to know - which threading primitives I can use for that (... what is behing the scenes of TPL). And what ContinueWith alternatives in TPL?
// will this handler block thread during async IO operation?
public class AsyncHandler : IHttpAsyncHandler
{
public void ProcessRequest(HttpContext ctx)
{
// not used
}
public bool IsReusable
{
get { return false; }
}
public IAsyncResult BeginProcessRequest(HttpContext ctx,
AsyncCallback cb,
object obj)
{
AsyncRequestState reqState =
new AsyncRequestState(ctx, cb, obj);
AsyncRequest ar = new AsyncRequest(reqState);
ThreadStart ts = new ThreadStart(ar.ProcessRequest);
Thread t = new Thread(ts);
t.Start();
return reqState;
}
public void EndProcessRequest(IAsyncResult ar)
{
AsyncRequestState ars = ar as AsyncRequestState;
if (ars != null)
{
// Some cleanup
}
}
}
class AsyncRequestState : IAsyncResult
{
public AsyncRequestState(HttpContext ctx,
AsyncCallback cb,
object extraData)
{
_ctx = ctx;
_cb = cb;
_extraData = extraData;
}
internal HttpContext _ctx;
internal AsyncCallback _cb;
internal object _extraData;
private bool _isCompleted = false;
private ManualResetEvent _callCompleteEvent = null;
internal void CompleteRequest()
{
_isCompleted = true;
lock (this)
{
if (_callCompleteEvent != null)
_callCompleteEvent.Set();
}
if (_cb != null)
_cb(this);
}
public object AsyncState
{ get { return (_extraData); } }
public bool CompletedSynchronously
{ get { return (false); } }
public bool IsCompleted
{ get { return (_isCompleted); } }
public WaitHandle AsyncWaitHandle
{
get
{
lock (this)
{
if (_callCompleteEvent == null)
_callCompleteEvent = new ManualResetEvent(false);
return _callCompleteEvent;
}
}
}
}
class AsyncRequest
{
private AsyncRequestState _asyncRequestState;
public AsyncRequest(AsyncRequestState ars)
{
_asyncRequestState = ars;
}
public void ProcessRequest()
{
//calling webservice or executing sql command asynchronously
AsyncIOOperationWithCallback(state =>
{
((AsyncRequestState)state.Context)._ctx.Response.Write("Operation completed");
_asyncRequestState.CompleteRequest();
}, _asyncRequestState);
}
}
Wait, by definition, is "blocking". You can wait on another thread, but you'd be blocking that thread. If by "non-blocking" you mean not blocking the UI, then you can wait on another thread and notify the UI thread via an event (which would handle InvokeRequired/BeginInvoke if necessary).
If that's not what you're asking, please provide more detail.
The way you typically deal with an asynchronous operation without blocking a thread is to have your asynchronous operation support a callback. The TPL really doesn't do anything magic here; if the underlying operations don't support some form of callback at some point, you're still stuck with something blocking eventually. The classic 'Begin/End' Asynchronous Programming Model handles this requirement perfectly fine.
When the TPL really shines is in providing better support for exception handling/aggregation and allowing more complex continuation models (such as ContinueWhenAll or ContinueWhenAny.) There is also new support for cancellation, including prevention of subsequent continuation. Continuation itself, though, is really nothing more than a callback in a fancier, cleaner package.
Related
I have two async functions, which I will call ChangeState() and DoThing(). Each of them awaits downstream async methods. These are called are from event handlers, so they will not block any other code while they execute. If ChangeState() is called, it's imperative that DoThing() does not do its thing until any previous ChangeState() has completed. ChangeState() could be called again while it's still executing. Any executions started before DoThing() should be completed before DoThing() can continue.
The reverse is also true; ChangeState() should wait until any previously running DoStuff() is complete.
How can I implement this without the danger of deadlocks?
I know awaits are not allowed inside of lock statements and that's for good reasons, which I why I'm not trying to recreate that functionality.
async void ChangeState(bool state)
{
//Wait here until any pending DoStuff() is complete.
await OutsideApi.ChangeState(state);
}
async void DoStuff()
{
//Wait here until any pending ChangeState() is complete.
await OutsideApi.DoStuff();
}
By your requirements seems something like ReaderWriterLock could help you. Also, since you have async methods you should use async lock. Unfortunately, there is no await ready ReaderWriterLock lock provided by the .NET framework itself. Luckily, you could take a look at the AsyncEx library or this article. The example using AsyncEx.
var readerWriterLock = new AsyncReaderWriterLock();
async void ChangeState(bool state)
{
using(await readerWriterLock.ReaderLockAsync())
{
await OutsideApi.ChangeState(state);
}
}
async void DoStuff()
{
using(await readerWriterLock.WriterLockAsync())
{
await OutsideApi.DoStuff();
}
}
n.b. This solution still has the limitation that DoStuff calls could not be concurrent, writer lock, but still the order of the calls and the requirement to finalize all DoStuff before ChangeState and vice versa will be fulfilled.(tip from #Scott Chamberlain to use both reader and writer lock)
You can use ManualResetEvent or AutoResetEvent to signal that a thread has finished so that another thread can continue the work.
Some samples can be found here and here:
EDIT: The first solution didn't meet the requirements.
Create a custom lock class.
This class keeps track of how many instances are running from which type (ChangeState and DoThing) and provides a way to check if a task can run.
public class CustomLock
{
private readonly int[] Running;
private readonly object _lock;
public CustomLock(int Count)
{
Running = new int[Count];
_lock = new object();
}
public void LockOne(int Task)
{
lock (_lock)
{
Running[Task]++;
}
}
public void UnlockOne(int Task)
{
lock (_lock)
{
Running[Task]--;
}
}
public bool Locked(int Task)
{
lock (_lock)
{
for (int i = 0; i < Running.Length; i++)
{
if (i != Task && Running[i] != 0)
return true;
}
return false;
}
}
}
Change the already existing code.
ChangeState will be task 0, and DoStuff will be task 1.
private CustomLock Lock = new CustomLock(2); //Create a new instance of the class for 2 tasks
async Task ChangeState(bool state)
{
while (Lock.Locked(0)) //Wait for the task to get unlocked
await Task.Delay(10);
Lock.LockOne(0); //Lock this task
await OutsideApi.ChangeState(state);
Lock.UnlockOne(0); //Task finished, unlock one
}
async Task DoStuff()
{
while (Lock.Locked(1))
await Task.Delay(10);
Lock.LockOne(1);
await OutsideApi.DoStuff();
Lock.UnlockOne(1);
}
While any ChangeState is running a new one can be started without waiting but when a DoStuff is called it will wait untill all ChangeStates finish, and this works the other way too.
I made for practice a synchronization primitive named KeyedLock, that allows concurrent asynchronous operations of only one key at a time. All other keys are queued, and unblocked later in batches (by key). The class is intended to be used like this:
KeyedLock _keyedLock;
async Task ChangeState(bool state)
{
using (await this._keyedLock.LockAsync("ChangeState"))
{
await OutsideApi.ChangeState(state);
}
}
async Task DoStuff()
{
using (await this._keyedLock.LockAsync("DoStuff"))
{
await OutsideApi.DoStuff();
}
}
For example the calls bellow:
await ChangeState(true);
await DoStuff();
await DoStuff();
await ChangeState(false);
await DoStuff();
await ChangeState(true);
...will be executed in this order:
ChangeState(true);
ChangeState(false); // concurrently with the above
ChangeState(true); // concurrently with the above
DoStuff(); // after completion of the above
DoStuff(); // concurrently with the above
DoStuff(); // concurrently with the above
The KeyedLock class:
class KeyedLock
{
private object _currentKey;
private int _currentCount = 0;
private WaitingQueue _waitingQueue = new WaitingQueue();
private readonly object _locker = new object();
public Task WaitAsync(object key, CancellationToken cancellationToken)
{
if (key == null) throw new ArgumentNullException(nameof(key));
lock (_locker)
{
if (_currentKey != null && key != _currentKey)
{
var waiter = new TaskCompletionSource<bool>();
_waitingQueue.Enqueue(new KeyValuePair<object,
TaskCompletionSource<bool>>(key, waiter));
if (cancellationToken != null)
{
cancellationToken.Register(() => waiter.TrySetCanceled());
}
return waiter.Task;
}
else
{
_currentKey = key;
_currentCount++;
return cancellationToken.IsCancellationRequested ?
Task.FromCanceled(cancellationToken) : Task.FromResult(true);
}
}
}
public Task WaitAsync(object key) => WaitAsync(key, CancellationToken.None);
public void Release()
{
List<TaskCompletionSource<bool>> tasksToRelease;
lock (_locker)
{
if (_currentCount <= 0) throw new InvalidOperationException();
_currentCount--;
if (_currentCount > 0) return;
_currentKey = null;
if (_waitingQueue.Count == 0) return;
var newWaitingQueue = new WaitingQueue();
tasksToRelease = new List<TaskCompletionSource<bool>>();
foreach (var entry in _waitingQueue)
{
if (_currentKey == null || entry.Key == _currentKey)
{
_currentKey = entry.Key;
_currentCount++;
tasksToRelease.Add(entry.Value);
}
else
{
newWaitingQueue.Enqueue(entry);
}
}
_waitingQueue = newWaitingQueue;
}
foreach (var item in tasksToRelease)
{
item.TrySetResult(true);
}
}
private class WaitingQueue :
Queue<KeyValuePair<object, TaskCompletionSource<bool>>>
{ }
public Task<Releaser> LockAsync(object key,
CancellationToken cancellationToken)
{
var waitTask = this.WaitAsync(key, cancellationToken);
return waitTask.ContinueWith(
(_, state) => new Releaser((KeyedLock)state),
this, cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default
);
}
public Task<Releaser> LockAsync(object key)
=> LockAsync(key, CancellationToken.None);
public struct Releaser : IDisposable
{
private readonly KeyedLock _parent;
internal Releaser(KeyedLock parent) { _parent = parent; }
public void Dispose() { _parent?.Release(); }
}
}
This seems like a good fit for a pair of ReaderWriterLockSlims.
private readonly ReaderWriterLockSlim changeStateLock = new ReaderWriterLockSlim();
private readonly ReaderWriterLockSlim doStuffLock = new ReaderWriterLockSlim();
One controls the access to ChangeState and the other controls the access to DoStuff.
The reader lock is used to signal that a method is being executed and the writer lock is used to signal that the other method is being executed. ReaderWriterLockSlim allows multiple reads but writes are exclusive.
Task.Yield is just to yield control back to the caller because ReaderWriterLockSlim's ar blocking.
async Task ChangeState(bool state)
{
await Task.Yield();
doStuffLock.EnterWriteLock();
try
{
changeStateLock.EnterReadLock();
try
{
await OutsideApi.ChangeState(state);
}
finally
{
changeStateLock.ExitReadLock();
}
}
finally
{
doStuffLock.ExitWriteLock();
}
}
async Task DoStuff()
{
await Task.Yield();
changeStateLock.EnterWriteLock();
try
{
doStuffLock.EnterReadLock();
try
{
await OutsideApi.DoStuff();
}
finally
{
doStuffLock.ExitReadLock();
}
}
finally
{
changeStateLock.ExitWriteLock();
}
}
I've updated an old project that was using SuperSocket to connect to old c++ servers. With the latest version (from 0.7.0 => 0.8.0.8) I got en exception when trying to reconnect (It says that the socket was opened on a different thread) I would like to have a class that enqueues tasks (First connection and reconnection) and runs them on a specific Thread.
I've seen this approach but when I try to run the task created as got an exception
ExecuteTask may not be called for a task which was previously queued to a different TaskScheduler.
Here's the class I've taken from the link above
public class SameThreadTaskScheduler : TaskScheduler, IDisposable
{
#region publics
public SameThreadTaskScheduler(string name)
{
scheduledTasks = new Queue<Task>();
threadName = name;
}
public override int MaximumConcurrencyLevel => 1;
public void Dispose()
{
lock (scheduledTasks)
{
quit = true;
Monitor.PulseAll(scheduledTasks);
}
}
#endregion
#region protected overrides
protected override IEnumerable<System.Threading.Tasks.Task> GetScheduledTasks()
{
lock (scheduledTasks)
{
return scheduledTasks.ToList();
}
}
protected override void QueueTask(Task task)
{
if (myThread == null)
myThread = StartThread(threadName);
if (!myThread.IsAlive)
throw new ObjectDisposedException("My thread is not alive, so this object has been disposed!");
lock (scheduledTasks)
{
scheduledTasks.Enqueue(task);
Monitor.PulseAll(scheduledTasks);
}
}
public void Queue(Task task)
{
QueueTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool task_was_previously_queued)
{
return false;
}
#endregion
private readonly Queue<System.Threading.Tasks.Task> scheduledTasks;
private Thread myThread;
private readonly string threadName;
private bool quit;
private Thread StartThread(string name)
{
var t = new Thread(MyThread) { Name = name };
using (var start = new Barrier(2))
{
t.Start(start);
ReachBarrier(start);
}
return t;
}
private void MyThread(object o)
{
Task tsk;
lock (scheduledTasks)
{
//When reaches the barrier, we know it holds the lock.
//
//So there is no Pulse call can trigger until
//this thread starts to wait for signals.
//
//It is important not to call StartThread within a lock.
//Otherwise, deadlock!
ReachBarrier(o as Barrier);
tsk = WaitAndDequeueTask();
}
for (;;)
{
if (tsk == null)
break;
TryExecuteTask(tsk);
lock (scheduledTasks)
{
tsk = WaitAndDequeueTask();
}
}
}
private Task WaitAndDequeueTask()
{
while (!scheduledTasks.Any() && !quit)
Monitor.Wait(scheduledTasks);
return quit ? null : scheduledTasks.Dequeue();
}
private static void ReachBarrier(Barrier b)
{
if (b != null)
b.SignalAndWait();
}
}
Here's how I call the task
public void RegisterServers()
{
sameThreadTaskScheduler.Queue(new Task(() =>
{
...something
}));
What am I doing wrong?
You must start a task to bind it to a TaskScheduler. In your code, you're manually queuing the task, doing it the other way around, so the task doesn't get bound to your task scheduler (or any at all), and TryExecuteTask will fail with that error. The description is rather cryptic, as any actual TaskScheduler is different from null.
For a task that you created without running, there are the overloads of Task.RunSynchronously and Task.Start that take a TaskScheduler.
For a task that starts running automatically, there are the overloads of TaskFactory.StartNew and TaskFactory<TResult>.StartNew that take a TaskScheduler.
For continuation tasks, there are the overloads of Task.ContinueWith, Task<TResult>.ContinueWith, TaskFactory.ContinueWhenAll and TaskFactory.ContinueWhenAny that take a TaskScheduler.
The overloads that don't take a task scheduler are equivalent to specifying TaskScheduler.Current. In the case of TaskFactory, this is true for the default Task.Factory or one created without a task scheduler at the time that the factory's method is called, otherwise the factory's task scheduler is used.
Contrast with the overloads of the newer Task.Run, which always use TaskScheduler.Default. According to most experienced folks, the thread-pool scheduler is desired more often as the default than TaskScheduler.Current which may be thread-bound, but it was too late to change the contract for the existing APIs.
here's my story: I have wcf service. It receives request with work to do. Each task is inserted into blocking queue. The server will take items from this queue periodically and do the work (totally async in different thread). In my "Do" service I need to know when "my" task was done. Like this:
public bool Do(int input)
{
// 1. Add task to the BlockingCollection queue
// 2. Block this thread from returning and observe/wait til my task is finished
return true;
}
Here's my suggestion/solution:
public bool Do(int input)
{
// 1. Create a ManualResetEvent object
// 2. Add this object to task
// 3. Add task to the BlockingCollection queue
// 4. Block this thread from returning - wait for ManualResetEvent object
return true;
}
So, there will be as many ManualResetEvent objects as there are tasks to do. I will literally have an array of sync objects. Is it good solution for my problem?
Or is there better synchronization class to use in my case? Like Wait and Pulse?
Thanks for help,
I'm sorry for the title. I didn't know how to ask this question in the title.
Your plan is good, however I would suggest not tying up a dedicated thread waiting for the work to be done. Switching from a new ManualResetEvent(false) to a new SemephoreSlim(0,1) will let you use WaitAsync() which would allow you to use async/await in your Do method and freeing up the thread to do other work. (UPDATE: This really should be a TaskCompletionSource instead of a Semaphore Slim, but I will not update this example, see the 2nd part below)
public async Task<bool> Do(int input)
{
using(var completion = new new SemephoreSlim(0,1))
{
var job = new JobTask(input, completion);
_workQueue.Add(job);
await completion.WaitAsync().ConfigureAwait(false);
return job.ResultData;
}
}
private void ProcessingLoop()
{
foreach(var job in _workQueue.GetConsumingEnumerable())
{
job.PerformWork(); //Inside PerformWork there is a _completion.Release(); call.
}
}
To make everything self contained you can change the SemaphoreSlim / TaskCompletionSource and put it inside the job then just return the job itself.
public JobTask Do(int input)
{
var job = new JobTask(input);
_workQueue.Add(job);
return job;
}
public class JobTask
{
private readonly int _input;
private readonly TaskCompletionSource<bool> _completionSource;
public JobTask(int input)
{
_input = input;
_completionSource = new TaskCompletionSource<bool>();
}
public void PerformWork()
{
try
{
// Do stuff here with _input.
_completionSource.TrySetResult(true);
}
catch(Exception ex)
{
_completionSource.TrySetException(ex);
}
}
public Task<bool> Work { get { return _completionSource.Task; } }
}
Let's say I have a UI thread and a background thread that subscribe to a custom thread-safe ObservableCollection that I created so that whenever the collection changes it executes the callback within the appropriate context.
Now let's say I add something to the collection (from either thread, doesn't matter which one) and it now has to marshall the callback to both threads. To execute the callback within the UI's context I can simply do a Dispatcher.Invoke(...) and it executes the callback within the UI's context; great.
Now I want to execute the callback within the background thread's context (don't ask me why, it may well be that whatever it's accessing is affinitized to that specific thread or has thread-local storage it needs to access); how would I do that?
Background threads don't have a dispatcher/message pumping mechanism so I can't use a dispatcher or SynchronizationContext, so how would one interrupt a background thread and have it execute my callback within its context?
EDIT: I keep getting answers that are obviously wrong so I must not have explained myself correctly. Forget the UI thread and UI dispatchers guys, they were meant to marshall calls to the UI thread, that's it! Imagine two worker threads A and B. If A modifies my collection then A is in charge of marshalling the callback to itself and to B. Executing the callback within A's context is easy since A was the one triggering it : simply call the delegate in place. Now A needs to marshall the callback to B... now what? Dispatcher and SynContext are useless in this situation.
A good idea might also be extending your own TaskScheduler, you will have to implement three methods:
QueueTask, TryExecuteTaskInline and GetScheduledTasks
you can read about it here
That way, anytime you need to run something on your dedicated thread you could just do:
Task.Factory.StartNew(() => { SomeAction }, SomeCancellationToken, TaskCreationOptions
new MyTaskSchedular());
and have it execute on your thread.
We have a component that must always run on the same STA background thread. We've achieved this by writing our own SynchronizationContext. This article is very helpful.
To summarise, you don't want to interrupt your worker thread, you want it to sit idle waiting for the next task that it should execute. You add jobs to a queue and it processes those jobs in order. The SynchronizationContext is a convenient abstraction around that idea. The SynchronizationContext is the owner of the worker thread - and the outside world does not interact with the thread directly: callers who want to execute a task on the worker thread make the request to the context which adds the job to the job queue. The worker is either working or polling the queue until another job is added, at which point it begins working again.
Update
Here is an example:
using System.Collections.Concurrent;
using System.Threading;
class LoadBalancedContext : SynchronizationContext
{
readonly Thread thread1;
readonly Thread thread2;
readonly ConcurrentQueue<JobInfo> jobs = new ConcurrentQueue<JobInfo>();
public LoadBalancedContext()
{
this.thread1 = new Thread(this.Poll) { Name = "T1" };
this.thread2 = new Thread(this.Poll) { Name = "T2" };
this.thread1.Start();
this.thread2.Start();
}
public override void Post(SendOrPostCallback d, object state)
{
this.jobs.Enqueue(new JobInfo { Callback = d, State = state });
}
void Poll()
{
while (true)
{
JobInfo info;
if (this.jobs.TryDequeue(out info))
{
info.Callback(info.State);
}
Thread.Sleep(100);
}
}
class JobInfo
{
public SendOrPostCallback Callback { get; set; }
public object State { get; set; }
}
}
Usage:
var context = new LoadBalancedContext();
SendOrPostCallback callback = x =>
{
Trace.WriteLine(Thread.CurrentThread.Name);
Thread.Sleep(200);
};
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
context.Post(callback, null);
Thread.Sleep(1000);
The Send case is slightly more involved as you will need to listen for a reset event.. This is not production quality, but should give you an idea ow what you need to do.
Hope that helps.
Forget dispatcher.invoke, forget the ui thread. Imagine I have 2 worker
threads and I want to dispatch my event to both worker threads; what
can I use?
I'd use two task schedulers for this (as #YuvalItzchakov's answer suggests), one for each thread. I'd also use a custom synchronization context for the worker thread, as #TheMouthofaCow's answer suggests.
That is, for a UI thread, I'd just save and use TaskScheduler.FromCurrentSynchronizationContext(). For the worker thread, I would start a thread and install a custom synchronization context on it, then use FromCurrentSynchronizationContext too.
Something like this (untested):
// UI thread
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
using (var worker = new ThreadWithPumpingSyncContext())
{
// call the worker thread
var result = await worker.Run(async () =>
{
// worker thread
await Task.Delay(1000);
// call the UI thread
await Task.Factory.StartNew(async () =>
{
// UI thread
await Task.Delay(2000);
MessageBox.Show("UI Thread!"),
// call the worker thread
await worker.Run(() =>
{
// worker thread
Thread.Sleep(3000)
});
// UI thread
await Task.Delay(4000);
}, uiTaskScheduler).Unwrap();
// worker thread
await Task.Delay(5000);
return Type.Missing; // or implement a non-generic version of Run
});
}
// ...
// ThreadWithSerialSyncContext renamed to ThreadWithPumpingSyncContext
class ThreadWithPumpingSyncContext : SynchronizationContext, IDisposable
{
public readonly TaskScheduler Scheduler; // can be used to run tasks on the pumping thread
readonly Task _mainThreadTask; // wrap the pumping thread as Task
readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
// track async void methods
readonly object _lock = new Object();
volatile int _pendingOps = 0; // the number of pending async void method calls
volatile TaskCompletionSource<Empty> _pendingOpsTcs = null; // to wait for pending async void method calls
public ThreadWithPumpingSyncContext()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
_mainThreadTask = Task.Factory.StartNew(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(this);
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
// pumping loop
foreach (var action in _actions.GetConsumingEnumerable())
action();
}
finally
{
SynchronizationContext.SetSynchronizationContext(null);
}
}, TaskCreationOptions.LongRunning);
Scheduler = tcs.Task.Result;
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
lock (_lock)
{
if (_pendingOpsTcs != null && _pendingOpsTcs.Task.IsCompleted)
throw new InvalidOperationException("OperationStarted"); // shutdown requested
_pendingOps++;
}
}
public override void OperationCompleted()
{
lock (_lock)
{
_pendingOps--;
if (0 == _pendingOps && null != _pendingOpsTcs)
_pendingOpsTcs.SetResult(Empty.Value);
}
}
public override void Post(SendOrPostCallback d, object state)
{
_actions.Add(() => d(state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
// Task start helpers
public Task Run(Action action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler);
}
public Task Run(Func<Task> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
public Task<T> Run<T>(Func<Task<T>> action, CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, this.Scheduler).Unwrap();
}
// IDispose
public void Dispose()
{
var disposingAlready = false;
lock (_lock)
{
disposingAlready = null != _pendingOpsTcs;
if (!disposingAlready)
{
// do not allow new async void method calls
_pendingOpsTcs = new TaskCompletionSource<Empty>();
if (0 == _pendingOps)
_pendingOpsTcs.TrySetResult(Empty.Value);
}
}
// outside the lock
if (!disposingAlready)
{
// wait for pending async void method calls
_pendingOpsTcs.Task.Wait();
// request the end of the pumping loop
_actions.CompleteAdding();
}
_mainThreadTask.Wait();
}
struct Empty { public static readonly Empty Value = default(Empty); }
}
This give you some sort of cooperative asynchronous execution between two threads.
How to make the completion of TaskCompletionSource.Task happen on specific TaskScheduler, when I call TaskCompletionSource.SetResult?
Currently, I'm using the idea I borrowed from this post:
static public Task<TResult> ContinueOnTaskScheduler<TResult>(
this Task<TResult> #this, TaskScheduler scheduler)
{
return #this.ContinueWith(
antecedent => antecedent,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
scheduler).Unwrap();
}
So whenever I would return TaskCompletionSource.Task to the caller, I now return TaskCompletionSource.Task.ContinueOnTaskScheduler(scheduler) instead.
Is it possible to somehow avoid this another level of indirection of ContinueWith?
It would be interesting to know your goals behind this. Anyway, if you like to avoid the overhead of ContinueWith (which I think is quite low), you'd probably have to come up with your own version of a pattern similar to TaskCompletionSource.
It's not that complex. E.g., something like Promise below can be used in the same way you use TaskCompletionSource, but would allow to provide a custom TaskScheduler for completion (disclaimer: almost untested):
public class Promise
{
readonly Task _task;
readonly CancellationTokenSource _cts;
readonly object _lock = new Object();
Action _completionAction = null;
// public API
public Promise()
{
_cts = new CancellationTokenSource();
_task = new Task(InvokeCompletionAction, _cts.Token);
}
public Task Task { get { return _task; } }
public void SetCompleted(TaskScheduler sheduler = null)
{
lock(_lock)
Complete(sheduler);
}
public void SetException(Exception ex, TaskScheduler sheduler = null)
{
lock (_lock)
{
_completionAction = () => { throw ex; };
Complete(sheduler);
}
}
public void SetException(System.Runtime.ExceptionServices.ExceptionDispatchInfo edi, TaskScheduler sheduler = null)
{
lock (_lock)
{
_completionAction = () => { edi.Throw(); };
Complete(sheduler);
}
}
public void SetCancelled(TaskScheduler sheduler = null)
{
lock (_lock)
{
// don't call _cts.Cancel() outside _completionAction
// otherwise the cancellation won't be done on the sheduler
_completionAction = () =>
{
_cts.Cancel();
_cts.Token.ThrowIfCancellationRequested();
};
Complete(sheduler);
}
}
// implementation
void InvokeCompletionAction()
{
if (_completionAction != null)
_completionAction();
}
void Complete(TaskScheduler sheduler)
{
if (Task.Status != TaskStatus.Created)
throw new InvalidOperationException("Invalid task state.");
_task.RunSynchronously(sheduler?? TaskScheduler.Current);
}
}
On a side note, this version has an override for SetException(ExceptionDispatchInfo edi), so you could propagate the active exception's state from inside catch:
catch(Exception ex)
{
var edi = ExceptionDispatchInfo.Capture(ex);
promise.SetException(edi);
}
It's easy to create a generic version of this, too.
There's a downside of this approach, though. A 3rd party can do promise.Task.Run or promise.Task.RunSynchronously, as the Task is exposed in the TaskStatus.Created state.
You could add a check for that into InvokeCompletionAction, or you could probably hide it using nested tasks / Task.Unwrap (although the latter would bring some overhead back).