In the following code, I am using the syntactic sugar provided by .net, the async/await approach, but read that this is not a good way of handling asynchronous operations within akka, and I should rather use PipeTo().
public class AggregatorActor : ActorBase, IWithUnboundedStash
{
#region Constructor
public AggregatorActor(IActorSystemSettings settings, IAccountComponent component, LogSettings logSettings) : base(settings, logSettings)
{
_accountComponent = component;
_settings = settings;
}
#endregion
#region Public Methods
public override void Listening()
{
ReceiveAsync<ProfilerMessages.ProfilerBase>(async x => await HandleMessage(x));
ReceiveAsync<ProfilerMessages.TimeElasped>(async x => await HandleMessage(x));
}
public override async Task HandleMessage(object msg)
{
msg.Match().With<ProfilerMessages.GetSummary>(async x =>
{
_sender = Context.Sender;
//Become busy. Stash
Become(Busy);
//Handle different request
await HandleSummaryRequest(x.UserId, x.CasinoId, x.GamingServerId, x.AccountNumber, x.GroupName);
});
msg.Match().With<ProfilerMessages.RecurringCheck>(x =>
{
_logger.Info("Recurring Message");
if (IsAllResponsesReceived())
{
BeginAggregate();
}
});
msg.Match().With<ProfilerMessages.TimeElasped>(x =>
{
_logger.Info("Time Elapsed");
BeginAggregate();
});
}
private async Task HandleSummaryRequest(int userId, int casinoId, int gsid, string accountNumber, string groupName)
{
try
{
var accountMsg = new AccountMessages.GetAggregatedData(userId, accountNumber, casinoId, gsid);
//AskPattern.AskAsync<AccountMessages.AccountResponseAll>(Context.Self, _accountActor, accountMsg, _settings.NumberOfMilliSecondsToWaitForResponse, (x) => { return x; });
_accountActor.Tell(accountMsg);
var contactMsg = new ContactMessages.GetAggregatedContactDetails(userId);
//AskPattern.AskAsync<Messages.ContactMessages.ContactResponse>(Context.Self, _contactActor, contactMsg, _settings.NumberOfMilliSecondsToWaitForResponse, (x) => { return x; });
_contactActor.Tell(contactMsg);
var analyticMsg = new AnalyticsMessages.GetAggregatedAnalytics(userId, casinoId, gsid);
//AskPattern.AskAsync<Messages.AnalyticsMessages.AnalyticsResponse>(Context.Self, _analyticsActor, analyticMsg, _settings.NumberOfMilliSecondsToWaitForResponse, (x) => { return x; });
_analyticsActor.Tell(analyticMsg);
var financialMsg = new FinancialMessages.GetAggregatedFinancialDetails(userId.ToString());
//AskPattern.AskAsync<Messages.FinancialMessages.FinancialResponse>(Context.Self, _financialActor, financialMsg, _settings.NumberOfMilliSecondsToWaitForResponse, (x) => { return x; });
_financialActor.Tell(financialMsg);
var verificationMsg = VerificationMessages.GetAggregatedVerification.Instance(groupName, casinoId.ToString(), userId.ToString(), gsid);
_verificationActor.Tell(verificationMsg);
var riskMessage = RiskMessages.GeAggregatedRiskDetails.Instance(userId, accountNumber, groupName, casinoId, gsid);
_riskActor.Tell(riskMessage);
_cancelable = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromMilliseconds(_settings.AggregatorTimeOut), Self, Messages.ProfilerMessages.TimeElasped.Instance(), Self);
_cancelRecurring = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(_settings.RecurringResponseCheck, _settings.RecurringResponseCheck, Self, Messages.ProfilerMessages.RecurringCheck.Instance(), Self);
}
catch (Exception ex)
{
ExceptionHandler(ex);
}
}
#endregion
}
As you can see in the example code, I am making use of async/await, and using the ReceiveAsync() method procided by Akka.net.
What is the purpose of ReceiveAsync(), if we cannot use async/await within an actor?
You can use async/await within an actor, however this requires a little bit of orchestration necessary to suspend/resume actor's mailbox until the the asynchronous task completes. This makes actor non-reentrant, which means that it will not pick any new messages, until the current task is finished. To make use of async/await within an actor you can either:
Use ReceiveAsync which can take async handlers.
Wrapping your async method call with ActorTaskScheduler.RunTask. This is usually useful in context of actor lifecycle methods (like PreStart/PostStop).
Keep in mind that this will work if a default actor message dispatcher is used, but it's not guaranteed to work, if an actor is configured to use different types of dispatchers.
Also there is a performance downside associated with using async/await inside actors, which is related to suspend/resume mechanics and lack of reentrancy of your actors. In many business cases it's not really a problem, but can sometimes be an issue in high-performance/low-latency workflows.
Related
I have a multi-threaded codebase that manipulates a stateful object that cannot be safely used concurrently.
However, it's hard to prevent concurrent usage, and easy to make mistakes, especially with all the various (useful!) async methods and possibilities that modern C# provides.
I can and do wrap the object and detect when simultaneous (and thus incorrect) access occurs, but that's a fairly fragile check, making it unattractive as a runtime check. When 2 or more async methods use the shared object, the check won't trigger unless they happen to do so exactly concurrently - so often that means that potentially faulty code will only rarely fault.
Worse, since the object is stateful, there exist state-transition patterns that are broken even without temporally overlapping access. Consider 2 threads both doing something like: get-value-from-object; perform-slow-computation-on-value; write-value-to-object. Any concurrency sanity checks on the object clearly won't trigger if two async contexts happen to do such operations when they happen to interleave (read or write) access to the shared object such they each access happens in a short window and doesn't coincide with other accesses. Essentially, this is a form of transaction isolation violation, but then in C# code, not a DB.
I want to see such errors more reliably, i.e. have some kind of fail-fast pattern that ideally triggers the programmer making the buggy change to see the error right away, and not after a tricky debugging session in production.
.net has a call-stack-like concept for async; this is clearly visible in the debugger, and there appears to be some level of api support for (or at least interaction with) it in the form of ExecutionContext, which enables stuff like AsyncLocal<> if I understand things correctly.
However, I have not found a way to detect when the conceptual async call stack turns into a tree; i.e. when the stateful object is accessed from 2 diverging branches of that async context. If I could detect such a tree-like state context, I could fail fast whenever an access to the stateful object is both preceded and succeeded by an access on another branch.
Is there some way to detect such async call context forking? Alternatively, how can I best fail-fast when mutable state is accessed from multiple async contexts, even when such access is not reliably temporally overlapping?
Is there some way to detect such async call context forking?
Yes
If your class has a member representing its state (e.g. private bool _isProcessing;), you can check that member to detect concurrent calls.
You need to make sure you read and write that member from a safe context, like from a lock(_someMutex) block.
Next Steps
Depending on what you want to do when you detect concurrent calls, you can implement your class in different ways.
If you want to handle concurrent calls by queuing them, you might want to just wrap the body of all your method around an async lock. You don't need an _isProcessing member with this approach.
private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private async Task<IDisposable> LockAsync()
{
await _semaphore.WaitAsync();
return new ActionDisposable(() => _semaphore.Release());
}
public async Task SomeAsyncMethod1()
{
using (await LockAsync())
{
// Do some async stuff.
}
}
public async Task SomeAsyncMethod2()
{
using (await LockAsync())
{
// Do some other async stuff.
}
}
If you want to discard concurrent calls, now the _isProcessing method is interesting.
private bool _isProcessing;
private object _mutex = new object();
public async Task SomeAsyncMethod1()
{
lock (_mutex)
{
if (_isProcessing)
{
// Concurrent call detected.
// Deal with it the way you want. (return null, throw an exception, whatever.)
throw new InvalidOperationException("Concurrent calls are not supported.");
}
_isProcessing = true;
}
// Do some async stuff.
_isProcessing = false;
}
public async Task SomeAsyncMethod2()
{
lock (_mutex)
{
if (_isProcessing)
{
// Concurrent call detected.
// Deal with it the way you want. (return null, throw an exception, whatever.)
throw new InvalidOperationException("Concurrent calls are not supported.");
}
_isProcessing = true;
}
// Do some other async stuff.
_isProcessing = false;
}
Maybe something like this can help? In your wrapper object you'd need to check AsyncLock.CanProceed, if it is false the caller didn't obtain a lock.
It would be possible to extend the code below to block while waiting for a lock.
using System;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
public class Program {
static Random _rnd = new();
public static async Task Main() {
for (int i = 0; i < 9; i++) {
var t1 = TestTask("1");
var t2 = TestTask("2");
var t3 = SubTask("3");
await Task.WhenAll(t1, t2, t3);
}
}
static async Task TestTask(string id) {
try {
await Task.Delay(_rnd.Next(50,250));
using (var alock = await AsyncLock.RequestLock()) {
Console.WriteLine($"TASK {id} {alock is not null} {AsyncLock.CanProceed}");
await Task.Delay(_rnd.Next(50,250));
await SubTask(id);
}
} catch {
Console.WriteLine($"LOCK FAILED {id}");
}
}
static async Task SubTask(string id) {
try {
await Task.Delay(_rnd.Next(50,250));
if (AsyncLock.CanProceed) {
Console.WriteLine($"TASK {id} SUB CAN PROCEED");
return;
} else {
using (var alock = await AsyncLock.RequestLock()) {
Console.WriteLine($"TASK {id} SUB {alock is not null} {AsyncLock.CanProceed}");
await Task.Delay(50);
}
}
} catch {
Console.WriteLine($"LOCK FAILED {id} SUB");
}
}
}
public class AsyncLock: IDisposable {
private static AsyncLock _lockGlobal;
private static AsyncLocal<AsyncLock> _lockAsync = new();
private static object _mutex = new();
public bool IsDisposed { get; private set; }
public static Task<AsyncLock> RequestLock() => RequestLock(new CancellationTokenSource().Token);
public static Task<AsyncLock> RequestLock(CancellationToken cancellationToken) {
lock (_mutex) {
if (_lockGlobal is not null) {
Console.WriteLine("BLOCK");
var tcs = new CancellationTokenSource();
tcs.Cancel();
return Task.FromCanceled<AsyncLock>(tcs.Token);
}
Console.WriteLine("LOCK");
return Task.FromResult(_lockAsync.Value = _lockGlobal = new AsyncLock());
}
}
public static bool CanProceed => _lockAsync.Value is not null;
private AsyncLock() {}
public void Dispose() {
lock (_mutex) {
if (IsDisposed) return;
Console.WriteLine("DISPOSE");
Debug.Assert(_lockGlobal == this);
Debug.Assert(_lockAsync.Value == this);
IsDisposed = true;
_lockAsync.Value = _lockGlobal = null;
}
}
}
If the use of Task.Run in this case justifiable ?
Currently I run this code in a WinForms app, but later on it will be used in a ASP.NET project as a HostedService/BackgroundService. I am not sure if this is comparable then.
After reading multiple blogs about async/await and Tasks I feel like the Task.Run(() => .. should be implemented in the calling method Manager.SyncLoop(). But what if the implementation of IConnection is truely asynchronous, wouldn't that be code smell ?
private async void button1_Click(object sender, EventArgs e)
{
// this should be handled by the BackgroudService, WinForms is used just for testing
var m = new Manager();
m.Connection = new ConnectionA();
m.ExecuteAsync();
}
}
public interface IConnection
{
Task<object> ReadAsync();
}
// assume that i cannot change this
public class SomeLib
{
private Random random = new Random();
public object SyncReading()
{
Thread.Sleep(5000);
return random.Next(); ;
}
}
public class ConnectionA : IConnection
{
private SomeLib lib = new SomeLib();
public Task<object> ReadAsync()
{
// is this usage of Task.Run ok?
var v = Task.Run(() => lib.SyncReading());
return v;
}
// this will block UI
//public Task<object> ReadAsync()
//{
// return Task.FromResult(lib.SyncReading());
//}
}
public class Manager
{
public IConnection Connection { get; set; }
public async Task ExecuteAsync()
{
await SyncLoop();
}
public async Task SyncLoop()
{
while (true)
{
var i = await Connection.ReadAsync();
await Task.Delay(2000);
}
}
}
First, can you change IConnection? Is this synchronous implementation the primary one, or is it just one of many?
If you can change IConnection, then make it synchronous, and you can use Task.Run in the implementation of ExecuteAsync.
If IConnection needs to remain asynchronous, then I would say to implement ConnectionA.ReadAsync synchronously. Then have the Task.Run in ExecuteAsync as normal. The key behind this technique is that an asynchronous (Task-returning) signature means that the implementation may be asynchronous, not that it must be asynchronous.
I am experiencing some confusion with Tasks and the async/await key words. I understand that you should NOT mix async and blocking code. Or at least what my interpretation of mixing them is:
Don't make calls to blocking API's from non- async methods. So here's my issue.
I am trying to await a method, then update the UI accordingly. The issue is that the only way to await an async method() call is from within and async method().
Here's an example:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
await RunValidation(options, cancellationTokenSource.Token);
Completed = true;
}));
}
}
This code runs the method properly and awaits. The issue is when I return. For some reason when setting the Complete flag the buttons dependent on this flag are not toggled. If I comment the await code, then the buttons are toggled correctly. So assumed I was not returning on the UI thread, so I tried using this code instead:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>(async (options) =>
{
Completed = false;
var cancellationTokenSource = new CancellationTokenSource();
var context = TaskScheduler.FromCurrentSynchronizationContext();
await RunValidation(options, cancellationTokenSource.Token).ContinueWith(t => Completed = true, context);
//Completed = true;
}));
}
}
Here is the RunValidation() method:
private async Task RunValidation(Options options, CancellationToken token)
{
await _someService.SomAsyncMethod(options, token));
}
If you notice, the ExecuteCommand has an async key word before the (options) parameter that is passed to the command. If I remove the async key word then I have to modify the call to the RunValidation() method. I still need it to await, so this is what I did:
private RelayCommand<Options> _executeCommand;
public RelayCommand<Options> ExecuteCommand
{
get
{
return _executeCommand ?? (_executeCommand = new RelayCommand<Options>((options) =>
{
Completed = false;
var context = TaskScheduler.FromCurrentSynchronizationContext();
var cancellationTokenSource = new CancellationTokenSource();
Task.Run(async () => await RunValidation(options, cancellationTokenSource.Token));
Completed = true;
}));
}
}
The problem with this code is that it doesn't await. So I am at a loss.
Can anyone shed some light on this for me please. I've spend 2 plus days on this and I am still here.
Thanks,
Tim
Here are the bindings to the Command Buttons.
private readonly Independent<bool> _completed = new Independent<bool>(true);
public bool Completed
{
get { return _completed; }
set { _completed.Value = value; }
}
private ICommand _doneCommand;
public ICommand DoneCommand
{
get
{
return _doneCommand ?? (_doneCommand = MakeCommand.When(() => Completed).Do(() =>
{
DoSomething();
}));
}
}
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
return _cancelCommand ??
(_cancelCommand = MakeCommand.When(() => !Completed).Do(() => DoSomthingElse()));
}
}
I am using the MakeCommand objects from the UpdateControls library from Michael Perry. They contain dependancy tracking that raises the CanExecuteChange events when the Complete property is changed.
Your first code is correct. Most likely you have an incorrect implementation for your Completed property. Your view model object should implement INotifyPropertyChanged. The easiest way to do this right is use a base class that provides the functionality. ReactiveUI is the nuget package I always use. Usage is as simple as
public class MyObject : ReactiveObject {
private bool _Completed;
public bool Completed {
get => _Completed;
set => this.RaiseAndSetIfChanged(ref _Completed, value);
}
}
This will make sure that notifications are raised to the UI when the property is changed.
If you want it to be more magic you can use ReactiveUI.Fody and then your code will reduce to
public class MyObject : ReactiveObject {
[Reactive]
public bool Completed { get; set;}
}
So the issue was in fact the third party library. I was using it to provide dependency tracking for the 2 buttons. So when the complete flag changed it raised the CanExecuteChange events for both buttons without me having write code to do it. Unfortunately it stopped working after introducing the async/await calls. I replaced the 2 MakeCommands with RelayCommands and raised the events myself and everything worked.
So thanks to everyone for your responses.
Tim
I'm pushing data updates/changes using IObservable, I have a method that gets the latest data from a database GetLatestElement, whenever anyone calls an UpdateElement and the data gets updated, a message is distributed over a messaging system.
So I'm creating an observable that emits the latest value, and then re-emits the new value when it receives the update event form the messaging system:
public IObservable<IElement> GetElement(Guid id)
{
return Observable.Create<T>((observer) =>
{
observer.OnNext(GetLatestElement(id));
// subscribe to internal or external update notifications
var messageCallback = (message) =>
{
// new update message recieved,
observer.OnNext(GetLatestElement(id));
}
messageService.SubscribeToTopic(id, messageCallback);
return Disposable.Create(() => Console.Writeline("Observer Disposed"));
});
}
My problem is that this is indefinite. These updates will potentially happen forever. Since I'm trying to get the system as state-less as possible, a new Observable is created for each request for GetElementType. This means the lifetime is dictated by the subscriber, not the source of the data.
I'll never call OnComplete() in the Observable, I want to complete when the Observer/User is done.
However, I need to call messageService.Unsubscribe(messageCallback); at some point in time in order to unsubscribe from the messages when the Observable is done with.
I could do this when the subscription is disposed, but then I can only subscribe a single time, which seems likely to introduce bugs.
How should this be done with Observables?
It seems there is some misunderstanding about how Observable.Create works. Whenever you call Subscribe on the result of your GetElement() - the body of Observable.Create is executed. So for each subscriber you have separate subscription to your messageService with separate callback to execute. If you unsubscribe - you only remove subscription of that subscriber. All other remain active, because they have their own messageCallback. That is assuming of course that messageService is implemented properly. Here is sample application illustrating that:
static IElement GetLatestElement(Guid id) {
return new Element();
}
public class Element : IElement {
}
public interface IElement {
}
class MessageService {
private Dictionary<Guid, Dictionary<Action<IElement>, CancellationTokenSource>> _subs = new Dictionary<Guid, Dictionary<Action<IElement>, CancellationTokenSource>>();
public void SubscribeToTopic(Guid id, Action<IElement> callback) {
var ct = new CancellationTokenSource();
if (!_subs.ContainsKey(id))
_subs[id] = new Dictionary<Action<IElement>, CancellationTokenSource>();
_subs[id].Add(callback, ct);
Task.Run(() =>
{
while (!ct.IsCancellationRequested) {
callback(new Element());
Thread.Sleep(500);
}
});
}
public void Unsubscribe(Guid id, Action<IElement> callback) {
_subs[id][callback].Cancel();
_subs[id].Remove(callback);
}
}
public static IObservable<IElement> GetElement(Guid id)
{
var messageService = new MessageService();
return Observable.Create<IElement>((observer) =>
{
observer.OnNext(GetLatestElement(id));
// subscribe to internal or external update notifications
Action<IElement> messageCallback = (message) =>
{
// new update message recieved,
observer.OnNext(GetLatestElement(id));
};
messageService.SubscribeToTopic(id, messageCallback);
return Disposable.Create(() => {
messageService.Unsubscribe(id, messageCallback);
Console.WriteLine("Observer Disposed");
});
});
}
public static void Main(string[] args) {
var ob = GetElement(Guid.NewGuid());
var sub1 = ob.Subscribe(c =>
{
Console.WriteLine("got element");
});
var sub2 = ob.Subscribe(c =>
{
Console.WriteLine("got element 2");
});
// at this point we see both subscribers receive messages
Console.ReadKey();
sub1.Dispose();
// first one is unsubscribed, but second one is still alive
Console.ReadKey();
}
So as I said it comments - I see no reason to complete your observable in this case.
As Evk pointed out, Observable.Create runs then disposes almost immediately. If you want to keep the messageService subscription open though, Rx can help you with that. Look at MessageObservableProvider. The rest is just to make things compile:
public class MessageObservableProvider
{
private MessageService messageService;
private Dictionary<Guid, IObservable<Unit>> _messageNotifications = new Dictionary<Guid, IObservable<Unit>>();
private IObservable<Unit> GetMessageNotifications(Guid id)
{
return Observable.Create<Unit>((observer) =>
{
Action<Message> messageCallback = _ => observer.OnNext(Unit.Default);
messageService.SubscribeToTopic(id, messageCallback);
return Disposable.Create(() =>
{
messageService.Unsubscribe(messageCallback);
Console.WriteLine("Observer Disposed");
});
});
}
public IObservable<IElement> GetElement(Guid id)
{
if(!_messageNotifications.ContainsKey(id))
_messageNotifications[id] = GetMessageNotifications(id).Publish().RefCount();
return _messageNotifications[id]
.Select(_ => GetLatestElement(id))
.StartWith(GetLatestElement(id));
}
private IElement GetLatestElement(Guid id)
{
throw new NotImplementedException();
}
}
public class IElement { }
public class Message { }
public class MessageService
{
public void SubscribeToTopic(Guid id, Action<Message> callback)
{
throw new NotImplementedException();
}
public void Unsubscribe(Action<Message> callback)
{
throw new NotImplementedException();
}
}
Your original Create implementation incorporated the functionality of a StartWith and a Select. I moved those out, so now the Observable.Create just returns a notification when a new message is available.
More importantly though, in GetElement there's now a .Publish().RefCount() call. This will leave the messageService subscription open (by not calling .Dispose()) as long as there's at least one child observable (subscription) hanging around.
I have a component that submits requests to a web-based API, but these requests must be throttled so as not to contravene the API's data limits. This means that all requests must pass through a queue to control the rate at which they are submitted, but they can (and should) execute concurrently to achieve maximum throughput. Each request must return some data to the calling code at some point in the future when it completes.
I'm struggling to create a nice model to handle the return of data.
Using a BlockingCollection I can't just return a Task<TResult> from the Schedule method, because the enqueuing and dequeuing processes are at either ends of the buffer. So instead I create a RequestItem<TResult> type that contains a callback of the form Action<Task<TResult>>.
The idea is that once an item has been pulled from the queue the callback can be invoked with the started task, but I've lost the generic type parameters by that point and I'm left using reflection and all kinds of nastiness (if it's even possible).
For example:
public class RequestScheduler
{
private readonly BlockingCollection<IRequestItem> _queue = new BlockingCollection<IRequestItem>();
public RequestScheduler()
{
this.Start();
}
// This can't return Task<TResult>, so returns void.
// Instead RequestItem is generic but this poses problems when adding to the queue
public void Schedule<TResult>(RequestItem<TResult> request)
{
_queue.Add(request);
}
private void Start()
{
Task.Factory.StartNew(() =>
{
foreach (var item in _queue.GetConsumingEnumerable())
{
// I want to be able to use the original type parameters here
// is there a nice way without reflection?
// ProcessItem submits an HttpWebRequest
Task.Factory.StartNew(() => ProcessItem(item))
.ContinueWith(t => { item.Callback(t); });
}
});
}
public void Stop()
{
_queue.CompleteAdding();
}
}
public class RequestItem<TResult> : IRequestItem
{
public IOperation<TResult> Operation { get; set; }
public Action<Task<TResult>> Callback { get; set; }
}
How can I continue to buffer my requests but return a Task<TResult> to the client when the request is pulled from the buffer and submitted to the API?
First, you can return Task<TResult> from Schedule(), you just need to use TaskCompletionSource for that.
Second, to get around the genericity issue, you can hide all of it inside (non-generic) Actions. In Schedule(), create an action using a lambda that does exactly what you need. The consuming loop will then execute that action, it doesn't need to know what's inside.
Third, I don't understand why are you starting a new Task in each iteration of the loop. For one, it means you won't actually get any throttling.
With these modifications, the code could look like this:
public class RequestScheduler
{
private readonly BlockingCollection<Action> m_queue = new BlockingCollection<Action>();
public RequestScheduler()
{
this.Start();
}
private void Start()
{
Task.Factory.StartNew(() =>
{
foreach (var action in m_queue.GetConsumingEnumerable())
{
action();
}
}, TaskCreationOptions.LongRunning);
}
public Task<TResult> Schedule<TResult>(IOperation<TResult> operation)
{
var tcs = new TaskCompletionSource<TResult>();
Action action = () =>
{
try
{
tcs.SetResult(ProcessItem(operation));
}
catch (Exception e)
{
tcs.SetException(e);
}
};
m_queue.Add(action);
return tcs.Task;
}
private T ProcessItem<T>(IOperation<T> operation)
{
// whatever
}
}