Make code run in parallel instead of awaiting every single data handler - c#

This is the websocket received message event handler. However these subscriptions are awaited and they are currently not running in parallel, so there is that process time prompt message. The idea is to make these data handlers run in parallel, which means Task.WhenAll should be used. The data handler is of type Func<T, ValueTask>, so it makes sense to be executed in parallel, so that if one of the data handlers contains await Task.Delay(5000), it shouldn't block the others.
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
foreach (var subscription in _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request)))
{
var userProcessTime = await MeasureUserProcessTime(async () => await subscription.DataHandler(messageEvent));
if (userProcessTime.TotalMilliseconds > 500)
{
_logger.LogTrace("Detected slow data handler ({UserProcessTimeMs} ms user code), consider offloading data handling to another thread. Data from this socket may arrive late or not at all if message processing is continuously slow.",
userProcessTime.TotalMilliseconds);
}
}
}
What about the version below?
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => subscription.DataHandler(messageEvent).AsTask());
_ = Task.WhenAll(handlers);
}
or
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => Task.Run(() => subscription.DataHandler(messageEvent)));
await Task.WhenAll(handlers);
}

The version with await Task.WhenAll is fine. I assume you still want to keep MeasureUserProcessTime, though, since that will give you useful warnings for long handlers.

I suppose somewhere you have some synchronization mechanism with SemaphoreSlim entities, but anyway... why don't you simply fire and forget if you don't do anything with the results of your tasks?
private void OnDataReceived(DataReceivedEventArgs e)
{
if (e is null)
{
_logger.Warning("!!!!!!!");
return;
}
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
foreach (var subscription in _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request)))
{
Task.Run(() =>
{
try
{
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => subscription.DataHandler(messageEvent).AsTask());
var userProcessTime = await MeasureUserProcessTime(async () => await subscription.DataHandler(messageEvent));
if (userProcessTime.TotalMilliseconds > 500)
{
_logger.LogTrace("Detected slow data handler ({UserProcessTimeMs} ms user code), consider offloading data handling to another thread. Data from this socket may arrive late or not at all if message processing is continuously slow.",
userProcessTime.TotalMilliseconds);
}
}
catch (Exception ex)
{
}
});
}
}

Related

How to use async func inside Nethereum StreamingWebSocketClient subscription

After getting log inside subscribe method I want to call async function, but Subscribe function only takes Action<FilterLog>, so using async-await keyword is not possible.
How can I use await keyword inside this subscription ?
code example:
public static async Task GetLogsTokenTransfer_Observable_Subscription()
{
using(var client = new StreamingWebSocketClient("wss://mainnet.infura.io/ws"))
{
var filterTransfers = Event<TransferEventDTO>.GetEventABI().CreateFilterInput();
var subscription = new EthLogsObservableSubscription(client);
subscription.GetSubscriptionDataResponsesAsObservable().Subscribe(log =>
{
var decoded = Event<TransferEventDTO>.DecodeEvent(log);
if (decoded != null)
{
MyAsyncMethodHere(); // Can not use await keyword !!!!
}
});
await client.StartAsync();
await subscription.SubscribeAsync(filterTransfers);
await Task.Delay(TimeSpan.FromMinutes(1));
await subscription.UnsubscribeAsync();
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
I'm not sure that this is the best approach but in my applications, i'm using Observable.FromAsync.
Smth like this:
subscription.GetSubscriptionDataResponsesAsObservable()
.Select(log => Observable.FromAsync(async () => {
await ProcessEvent(log);
}))
.Concat()
.Subscribe();
The Concat method is pretty important here, because it's ensures that the will be no overlapping in task execution

C# Syncing HashSet withing Action

I have a question I have a simple subscriber and simple publisher they look like:
public async Task SendRequest()
{
var topic = "SomeTopic";
var requestHash = Helpers.ReturnUniqueKey(DateTime.Now, topic);
requestKeys.Add(requestHash);
Console.WriteLine($"Key count {requestKeys.Count}");
var responseHandler = new Action<ResponseMessage>(response =>
{
Console.WriteLine($"Key count {requestKeys.Count}");
foreach (var key in requestKeys)
{
Console.WriteLine($"Response { BitConverter.ToString(response.IdentyficationHash) } - Key { BitConverter.ToString(key) }");
if (!key.SequenceEqual(response.IdentyficationHash)) return;
requestKeys.Remove(key);
}
});
bus.Subscribe(BusController.ManualRequest, responseHandler, configuration => configuration.WithTopic(BusController.ManualRequest));
bus.Publish(someRequest, topic);
async Task WaitForItToWorkAsync()
{
var retry = 0;
var complete = false;
while (!complete)
{
if (retry >= 20) return ; // Ill ass some msg leater
complete = !requestKeys.Contains(requestHash);
retry += 1;
await Task.Delay(1000);
}
return // Ill ass some msg leater
}
await WaitForItToWorkAsync()
}
Main idea is I am sending message to some service with some request and wait for arrival (I know i can use rpc, but there can be any services and rpc dos not support topic), well this path works, problem is that requestKeys HashSet it a field in a class
private readonly HashSet<byte[]> requestKeys;
As you see on each method invocation I add Key to that field, if I'll make first request it works fine, but other will not update this key collection, I mean outside Action it is updated, but outside it is a problem. What can I do to solve this?
If you want to prevent SendRequest() from completing until you have received a response, you could use a SemaphoreSlim instead of adding and removing keys from a HashSet, e.g.:
public async Task SendRequest()
{
var topic = "SomeTopic";
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0, 1);
var responseHandler = new Action<ResponseMessage>(response =>
{
//signal that the response has arrived
semaphoreSlim.Release();
});
bus.Subscribe(BusController.ManualRequest, responseHandler, configuration => configuration.WithTopic(BusController.ManualRequest));
bus.Publish(someRequest, topic);
//wait for the response to arrive
await semaphoreSlim.WaitAsync();
semaphoreSlim.Dispose();
}

Subscribe to observable and await later

Following would NOT catch the emitted value:
someObservable = Observable.FromEventPattern(this, "MyEvent");
FireMyEvent("foo");
await someObservable.FirstOrDefaultAsync(e => e.Args == "foo");
So I've come across this pattern in my unit tests:
var tsc = new TaskCompletionSource<bool>();
var callback = someObservable.FirstOrDefaultAsync(...);
using (callback.Subscribe(e => tsc.SetResult(e.Args)))
{
FireMyEvent("foo");
tsc.Task.Wait(1000);
}
Assert.AreEqual("foo", tsc.Task.Result);
Basically I need to subscribe, then trigger the action and then await the subscribed observable.
Is it possible to simplify this subscribe/await without introducing new Task, or WaitHandle?
I'm not sure what exactly you're trying to do, but if I understand the basic idea then this works:
var subject = new Subject<Unit>();
var callback = subject.FirstOrDefaultAsync();
using (callback.Subscribe(_ => { }, () => Console.WriteLine("Done.")))
{
subject.OnNext(Unit.Default);
}
Can you explain your use-case more fully?
I don't really understand your assumption, that the await won't catch the result.
You have to await the event before it gets fired
someObservable = Observable.FromEventPattern(this, "MyEvent");
Task.Delay(1000).ContinueWith(_ => FireMyEvent("foo"));
var result = await someObservable.FirstOrDefaultAsync(e => e.Args == "foo");
Your result ist now a EventPattern. However I would recommend a more type safer FromEventPattern-Overload:
var eventObservable = Observable
.FromEventPattern<YourEventArgs>(
h => MyEvent += h,
h => MyEvent -= h);

Avoiding await in foreach loop

I am trying to optimize this code to decrease the time taken to complete the forloop. In this case, CreateNotification() takes a long time and using async await does not improve performance as each asynchronous call is being awaited. I would like to use Task.WhenAll() to optimize the code. How can I do this?
foreach (var notification in notificationsInput.Notifications)
{
try
{
var result = await CreateNotification(notification);
notification.Result = result;
}
catch (Exception exception)
{
notification.Result = null;
}
notifications.Add(notification);
}
You can call Select on the collection whose elements you want to process in parallel, passing an asynchronous delegate to it. This asynchronous delegate would return a Task for each element that's processed, so you could then call Task.WhenAll on all these tasks. The pattern is like so:
var tasks = collection.Select(async (x) => await ProcessAsync(x));
await Task.WhenAll(tasks);
For your example:
var tasks = notificationsInput.Notifications.Select(async (notification) =>
{
try
{
var result = await CreateNotification(notification);
notification.Result = result;
}
catch (Exception exception)
{
notification.Result = null;
}
});
await Task.WhenAll(tasks);
This assumes that CreateNotification is thread-safe.
Update
You will need to install DataFlow to use this solution
https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/
Depending on what CreateNotification is and whether you want to run this in parallel.
You could use a DataFlow ActionBlock, it will give you the best of both worlds if this is IO bound or Mix IO/CPU bound operations and let you run async and in parallel
public static async Task DoWorkLoads(NotificationsInput notificationsInput)
{
var options = new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 50
};
var block = new ActionBlock<Something>(MyMethodAsync, options);
foreach (var notification in notificationsInput.Notifications)
block.Post(notification);
block.Complete();
await block.Completion;
}
...
public async Task MyMethodAsync(Notification notification)
{
var result = await CreateNotification(notification);
notification.Result = result;
}
Add pepper and salt to taste.
I think this ought to be equivalent to your code:
var notifications = new ConcurrentBag<Notification>();
var tasks = new List<Task>();
foreach (var notification in notificationsInput.Notifications)
{
var task = CreateNotification(notification)
.ContinueWith(t =>
{
if (t.Exception != null)
{
notification.Result = null;
}
else
{
notification.Result = t.Result;
}
notifications.Add(notification);
});
tasks.Add(task);
}
await Task.WhenAll(tasks);
.ContinueWith( will receive the completed/failed task from CreateNotification(, and is itself a task. We add the ContinueWith task to a list and use that in the WhenAll(.
I'm using a ConcurrentBag for notifications so that you can add from multiple threads safely. If you want to turn this into a regular list, you can call var regularListNotifications = notifications.ToList(); (assuming you have a using for LINQ).

How to handle task cancellation in the TPL

Good day! I am writing a helper library for WinForms UI. Started using TPL async/await mechanism and got a problem with this kind of code example :
private SynchronizationContext _context;
public void UpdateUI(Action action)
{
_context.Post(delegate { action(); }, null);
}
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); });
Action usefulWork = () =>
{
try
{
Thread.Sleep(taskAwait);
cancellationSource.Cancel();
}
catch { }
};
Action progressUpdate = () =>
{
int i = 0;
while (i < 10)
{
UpdateUI(() => { button2.Text = "Processing " + i.ToString(); });
Thread.Sleep(progressRefresh);
i++;
}
cancellationSource.Cancel();
};
var usefulWorkTask = new Task(usefulWork, cancellationSource.Token);
var progressUpdateTask = new Task(progressUpdate, cancellationSource.Token);
try
{
cancellationSource.Token.ThrowIfCancellationRequested();
Task tWork = Task.Factory.StartNew(usefulWork, cancellationSource.Token);
Task tProgress = Task.Factory.StartNew(progressUpdate, cancellationSource.Token);
await Task.Run(() =>
{
try
{
var res = Task.WaitAny(new[] { tWork, tProgress }, cancellationSource.Token);
}
catch { }
}).ConfigureAwait(false);
}
catch (Exception ex)
{
}
await Task.Run(() => { UpdateUI(() => { button2.Text = "button2"; }); });
}
Basically, the idea is to run two parallel tasks - one is for, say, progress bar or whatever update and a sort of timeout controller, the other is the long running task itself. Whichever task finishes first cancels the other one. So, there should not be a problem to cancel the "progress" task as it has a loop in which I can check if task is marked cancelled. The problem is with the long running one. It could be Thread.Sleep() or SqlConnection.Open(). When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
As the cluttered code example may suggest, I have tried a bunch of variants and none given me a desired effect. Something like Task.WaitAny() freezes UI... Is there a way to make that cancellation work or may be even a different approach to code these things?
UPD:
public static class Taskhelpers
{
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
await task;
}
}
.....
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
var usefulWorkTask = Task.Run(async () =>
{
try
{
System.Diagnostics.Trace.WriteLine("WORK : started");
await Task.Delay(taskAwait).WithCancellation(cancellationToken);
System.Diagnostics.Trace.WriteLine("WORK : finished");
}
catch (OperationCanceledException) { } // just drop out if got cancelled
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("WORK : unexpected error : " + ex.Message);
}
}, cancellationToken);
var progressUpdatetask = Task.Run(async () =>
{
for (var i = 0; i < 25; i++)
{
if (!cancellationToken.IsCancellationRequested)
{
System.Diagnostics.Trace.WriteLine("==== : " + i.ToString());
await Task.Delay(progressRefresh);
}
}
},cancellationToken);
await Task.WhenAny(usefulWorkTask, progressUpdatetask);
cancellationSource.Cancel();
By modifying for (var i = 0; i < 25; i++) limit of i I imitate whether long running task finishes before the progress task or otherwise. Works as desired. The WithCancellation helper method does the job, although two sort of 'nested' Task.WhenAny look suspicious for now.
I agree with all the points in Paulo's answer - namely, use modern solutions (Task.Run instead of Task.Factory.StartNew, Progress<T> for progress updates instead of manually posting to the SynchronizationContext, Task.WhenAny instead of Task.WaitAny for asynchronous code).
But to answer the actual question:
When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
There are two parts to this:
How do I write code that responds to a cancellation request?
How do I write code that ignores any responses after the cancellation?
Note that the first part deals with cancelling the operation, and the second part is actually dealing with cancelling the waiting for the operation to complete.
First things first: support cancellation in the operation itself. For CPU-bound code (i.e., running a loop), periodically call token.ThrowIfCancellationRequested(). For I/O-bound code, the best option is to pass the token down to the next API layer - most (but not all) I/O APIs can (should) take cancellation tokens. If this isn't an option, then you can either choose to ignore the cancellation, or you can register a cancellation callback with token.Register. Sometimes there's a separate cancellation method you can call from your Register callback, and sometimes you can make it work by disposing the object from the callback (this approach often works because of a long-standing Win32 API tradition of cancelling all I/O for a handle when that handle is closed). I'm not sure if this will work for SqlConnection.Open, though.
Next, cancelling the wait. This one is relatively simple if you just want to cancel the wait due to a timeout:
await Task.WhenAny(tWork, tProgress, Task.Delay(5000));
When you write something like await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); }); on your button2_Click, you are, from the UI thread, scheduling an action to a thread poll thread that posts an action to the UI thread. If you called the action directly, it would be quickier because it wouldn't have two context switchings.
ConfigureAwait(false) causes the synchronization context to not being captured. I should not be used inside UI methods because, you most certainely, want to do some UI work on the continuation.
You shouldn't use Task.Factory.StartNew instead of Task.Run unless you absolutely have a reason to. See this and this.
For progress updates, consider using the Progress<T> class, because it captures the synchronization context.
Maybe you should try something like this:
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var cancellationSource = new CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
button2.Text = "Processing...";
var usefullWorkTask = Task.Run(async () =>
{
try
{
await Task.Dealy(taskAwait);
}
catch { }
},
cancellationToken);
var progress = new Progress<imt>(i => {
button2.Text = "Processing " + i.ToString();
});
var progressUpdateTask = Task.Run(async () =>
{
for(var i = 0; i < 10; i++)
{
progress.Report(i);
}
},
cancellationToken);
await Task.WhenAny(usefullWorkTask, progressUpdateTask);
cancellationSource.Cancel();
}
I think you need to check IsCancellationRequested in the progressUpdate Action.
As to how to do what you want, this blog discusses an Extension method WithCancellation that will make it so that you stop waiting for your long running task.

Categories