How to cancel running hangfire job? - c#

This is my codebase. I need to stop running job. I've tried BackgroundJob.Delete method and send current jobId but it didn't help.It just deletes the job but not cancelling it. I can run multiple jobs and should be able to stop each of them from UI. I tried to use CancellationToken but on the UI I am using AJAX to send request and it takes some milliseconds so I can't even abort this request. Can somebody suggest something please? Thanks.
public class JobController : Controller {
public void InternalLogic(int id, CancellationToken cancellationToken)
{
foreach (var item in collection)
{
if (someCondition)
{
if (cancellationToken.IsCancellationRequested)
{
//some logic
break;
}
//continue working
}
}
}
public void RunLogic(int id, CancellationToken cancellationToken)
{
//some logic
InternalLogic(id,cancellationToken);
}
public void Run(int id, CancellationToken cancellationToken)
{
var jobId = BackgroundJob.Enqueue(() => this.RunLogic(id, cancellationToken));
}
}

I'm not sure if I understand your question exactly, but I believe that the CancellationToken you are using is short lived. The request to start the job executes and completes at which point the CancellationToken you are using is useless and will never be cancelled since the request that created it has long since completed successfully and discarded its CancellationTokenSource.
If you want to use CancellationToken, you will need to create your own CancellationTokenSource, keep track of it and cancel the job yourself when you need to cancel it.
Perhaps something similar in concept to:
public class JobController : Controller {
//Not safe, for demonstration purposes only
private static Dictionary<int, CancellationTokenSource> _dictionary = new Dictionary<int, CancellationTokenSource>();
private void InternalLogic(int id, CancellationToken cancellationToken)
{
foreach (var item in collection)
{
if (someCondition)
{
if (cancellationToken.IsCancellationRequested)
{
//some logic
break;
}
//continue working
}
}
}
private void RunLogic(int id, CancellationToken cancellationToken)
{
//some logic
InternalLogic(id,cancellationToken);
}
//Client requests that a job is started
public void Run(int id, CancellationToken cancellationToken)
{
var cts = new CancellationTokenSource()
var jobId = BackgroundJob.Enqueue(() => this.RunLogic(id, cts.Token));
}
//Client requests that a job is cancelled.
public void Cancel(int id, CancellationToken cancellationToken)
{
_dictionary[id].Cancel(); //or perhaps track using jobId?
}
}
This is not a production ready solution - you will want to make sure only the correct user can cancel the correct jobs, and will probably want to find something more elegant and thread safe than a static dictionary.
This solution is not specific to Hangfire - it's just the general idea of how you might signal cancellation of a long running, asynchronous background task.

Related

Cancel an internal task using CancellationToken in ASP.NET Core2.2

I have a method that contains an infinite loop. I call that method from main method and I want to cancel it if it takes more that 2 seconds.
I tried these approaches
Task.WhenAny with cancellation of the non completed tasks and timeout
How can I cancel Task.WhenAll?
How to cancel a task using a CancellationToken and await Task.WhenAny
and lots of other links, but non has worked.
here is my code
static void Main(string[] args)
{
var cts = new CancellationTokenSource(2000);
var task = Task.Run(() => RunTask(), cts.Token);
await task;
}
public static void RunTask()
{
while (true)
{
Console.WriteLine("in while");
}
}
I also tried to cancel my token but it didn't work.
It is not possible to Dispose the task.
I don't have access to RunTask source code. I just can run it.
I cannot send CancellationToken to my method. How can I terminate RunTask() method?
Cancellation tokens are a way to request cancellation. It's up to the running code to accept that request and actually stop the work. It's not like killing a process. For example, if you had something like:
public static void RunTask(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("in while");
}
}
Then, when it's canceled, the loop will end. However, given that the current code doesn't offer any method of canceling and you cannot modify it, then there is no way to cancel it.
What about this approach:
static void Main(string[] args)
{
var cts = new CancellationTokenSource(2000);
var task = Task.Run(() => RunTask(cts.Token), cts.Token);
await task;
}
public static void RunTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("in while");
}
}

How can I get the result of my work in a Task

I need to do a work in a Task (infinite loop for monitoring) but how can I get the result of this work?
My logic to do this stuff i wrong? This is a scope problem I think.
There is an example simplified:
The variable is "first" and I want "edit"
namespace my{
public class Program{
public static void Main(string[] args){
Logic p = new Logic();
Task t = new Task(p.process);
t.Start();
Console.WriteLine(p.getVar());// result="first"
}
}
public class Logic{
public string test = "first";
public void process(){
while(true){
//If condition here
this.test = "edit";
}
}
public String getVar(){
return this.test;
}
}
}
It can be done using custom event. In your case it can be something like:
public event Action<string> OnValueChanged;
Then attach to it
p.OnValueChanged += (newValue) => Console.WriteLine(newValue);
And do not forget to fire it
this.test = "edit";
OnValueChanged?.Invoke(this.test);
Tasks aren't threads, they don't need a .Start call to start them. All examples and tutorials show the use of Task.Run or Task.StartNew for a reason - tasks are a promise that a function will execute at some point in the future and produce a result. They will run on threads pulled from a ThreadPool when a Task Scheduler decides they should. Creating cold tasks and calling .Start doesn't guarantee they will start, it simply makes the code a lot more difficult to read.
In the simplest case, polling eg a remote HTTP endpoint could be as simple as :
public static async Task Main()
{
var client=new HttpClient(serverUrl);
while(true)
{
var response=await client.GetAsync(relativeServiceUrl);
if(!response.IsSuccessStatusCode)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
There's no need to start a new Task because GetAsync is asynchronous. WCF and ADO.NET also provide asynchronous execution methods.
If there's no asynchronous method to call, or if we need to perform some heavey work before the async call, we can use Task.Run to start a method in parallel and await for it to finish:
public bool CheckThatService(string serviceUrl)
{
....
}
public static async Task Main()
{
var url="...";
//...
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
What if we want to test multiple systems in parallel? We can start multiple tasks in parallel, await all of them to complete and check their results:
public static async Task Main()
{
var urls=new[]{"...","..."};
//...
while(true)
{
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses)
{
///Check the value, due something
}
await Task.Delay(1000);
}
}
Task.WhenAll returns an array with the results in the order the tasks were created. This allows checking the index to find the original URL. A better idea would be to return the result and url together, eg using tuples :
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
return (true,url);
}
The code wouldn't change a lot:
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses.Where(resp=>!resp.ok))
{
///Check the value, due something
}
What if we wanted to store the results from all the calls? We can't use a List or Queue because they aren't thread safe. We can use a ConcurrentQueue instead:
ConcurrentQueue<string> _results=new ConcurrentQueue<string>();
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
_results.Enqueue(someresult);
return (true,url);
}
If we want to report progress regularly we can use IProgress<T> as shown in Enabling Progress and Cancellation in Async APIs.
We could put all the monitoring code in a separate method/class that accepts an IProgress< T> parameter with a progress object that can report success, error messages and the URL that caused them, eg :
class MonitorDTO
{
public string Url{get;set;}
public bool Success{get;set;}
public string Message{get;set;}
public MonitorDTO(string ulr,bool success,string msg)
{
//...
}
}
class MyMonitor
{
string[] _urls=url;
public MyMonitor(string[] urls)
{
_urls=url;
}
public Task Run(IProgress<MonitorDTO> progress)
{
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
_progress.Report(new MonitorDTO(ok,url,"some message");
}
await Task.Delay(1000);
}
}
}
This class could be used in this way:
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
await monitor.Run(progress);
}
Enabling Progress and Cancellation in Async APIs shows how to use the CancellationTokenSource to implement another important part of a monitoring class - cancelling it. The monitoring method could check the status of a cancellation token periodically and stop monitoring when it's raised:
public Task Run(IProgress<MonitorDTO> progress,CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
//...
}
}
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
var cts = new CancellationTokenSource();
//Not awaiting yet!
var monitorTask=monitor.Run(progress,cts.Token);
//Keep running until the first keypress
Console.ReadKey();
//Cancel and wait for the monitoring class to gracefully stop
cts.Cancel();
await monitorTask;
In this case the loop will exit when the CancellationToken is raised. By not awaiting on MyMonitor.Run() we can keep working on the main thread until an event occurs that signals monitoring should stop.
The getVar method is executed before the process method.
Make sure that you wait until your task is finished before you call the getVar method.
Logic p = new Logic();
Task t = new Task(p.process);
t.Start();
t.Wait(); // Add this line!
Console.WriteLine(p.getVar());
If you want to learn more about the Wait method, please check this link.

Raise async event in EF's DbContext.SaveChangesAsync()

I'm using EF Core, in an ASP.NET Core environment. My context is registered in my DI container as per-request.
I need to perform extra work before the context's SaveChanges() or SaveChangesAsync(), such as validation, auditing, dispatching notifications, etc. Some of that work is sync, and some is async.
So I want to raise a sync or async event to allow listeners do extra work, block until they are done (!), and then call the DbContext base class to actually save.
public class MyContext : DbContext
{
// sync: ------------------------------
// define sync event handler
public event EventHandler<EventArgs> SavingChanges;
// sync save
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
// raise event for sync handlers to do work BEFORE the save
var handler = SavingChanges;
if (handler != null)
handler(this, EventArgs.Empty);
// all work done, now save
return base.SaveChanges(acceptAllChangesOnSuccess);
}
// async: ------------------------------
// define async event handler
//public event /* ??? */ SavingChangesAsync;
// async save
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
// raise event for async handlers to do work BEFORE the save (block until they are done!)
//await ???
// all work done, now save
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
As you can see, it's easy for SaveChanges(), but how do I do it for SaveChangesAsync()?
So I want to raise a sync or async event to allow listeners do extra work, block until they are done (!), and then call the DbContext base class to actually save.
As you can see, it's easy for SaveChanges()
Not really... SaveChanges won't wait for any asynchronous handlers to complete. In general, blocking on async work isn't recommended; even in environments such as ASP.NET Core where you won't deadlock, it does impact your scalability. Since your MyContext allows asynchronous handlers, you'd probably want to override SaveChanges to just throw an exception. Or, you could choose to just block, and hope that users won't use asynchronous handlers with synchronous SaveChanges too much.
Regarding the implementation itself, there are a few approaches that I describe in my blog post on async events. My personal favorite is the deferral approach, which looks like this (using my Nito.AsyncEx.Oop library):
public class MyEventArgs: EventArgs, IDeferralSource
{
internal DeferralManager DeferralManager { get; } = new DeferralManager();
public IDisposable GetDeferral() => DeferralManager.DeferralSource.GetDeferral();
}
public class MyContext : DbContext
{
public event EventHandler<MyEventArgs> SavingChanges;
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
// You must decide to either throw or block here (see above).
// Example code for blocking.
var args = new MyEventArgs();
SavingChanges?.Invoke(this, args);
args.DeferralManager.WaitForDeferralsAsync().GetAwaiter().GetResult();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var args = new MyEventArgs();
SavingChanges?.Invoke(this, args);
await args.DeferralManager.WaitForDeferralsAsync();
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
// Usage (synchronous handler):
myContext.SavingChanges += (sender, e) =>
{
Thread.Sleep(1000); // Synchronous code
};
// Usage (asynchronous handler):
myContext.SavingChanges += async (sender, e) =>
{
using (e.GetDeferral())
{
await Task.Delay(1000); // Asynchronous code
}
};
There is a simpler way (based on this).
Declare a multicast delegate which returns a Task:
namespace MyProject
{
public delegate Task AsyncEventHandler<TEventArgs>(object sender, TEventArgs e);
}
Update the context (I'm only showing async stuff, because sync stuff is unchanged):
public class MyContext : DbContext
{
public event AsyncEventHandler<EventArgs> SavingChangesAsync;
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var delegates = SavingChangesAsync;
if (delegates != null)
{
var tasks = delegates
.GetInvocationList()
.Select(d => ((AsyncEventHandler<EventArgs>)d)(this, EventArgs.Empty))
.ToList();
await Task.WhenAll(tasks);
}
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
}
The calling code looks like this:
context.SavingChanges += OnContextSavingChanges;
context.SavingChangesAsync += OnContextSavingChangesAsync;
public void OnContextSavingChanges(object sender, EventArgs e)
{
someSyncMethod();
}
public async Task OnContextSavingChangesAsync(object sender, EventArgs e)
{
await someAsyncMethod();
}
I'm not sure if this is a 100% safe way to do this. Async events are tricky. I tested with multiple subscribers, and it worked. My environment is ASP.NET Core, so I don't know if it works elsewhere.
I don't know how it compares with the other solution, or which is better, but this one is simpler and makes more sense to me.
EDIT: this works well if your handler doesn't change shared state. If it does, see the much more robust approach by #stephencleary above
I'd suggest a modification of this async event handler
public AsyncEvent SavingChangesAsync;
usage
// async save
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
await SavingChangesAsync?.InvokeAsync(cancellationToken);
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
where
public class AsyncEvent
{
private readonly List<Func<CancellationToken, Task>> invocationList;
private readonly object locker;
private AsyncEvent()
{
invocationList = new List<Func<CancellationToken, Task>>();
locker = new object();
}
public static AsyncEvent operator +(
AsyncEvent e, Func<CancellationToken, Task> callback)
{
if (callback == null) throw new NullReferenceException("callback is null");
//Note: Thread safety issue- if two threads register to the same event (on the first time, i.e when it is null)
//they could get a different instance, so whoever was first will be overridden.
//A solution for that would be to switch to a public constructor and use it, but then we'll 'lose' the similar syntax to c# events
if (e == null) e = new AsyncEvent();
lock (e.locker)
{
e.invocationList.Add(callback);
}
return e;
}
public static AsyncEvent operator -(
AsyncEvent e, Func<CancellationToken, Task> callback)
{
if (callback == null) throw new NullReferenceException("callback is null");
if (e == null) return null;
lock (e.locker)
{
e.invocationList.Remove(callback);
}
return e;
}
public async Task InvokeAsync(CancellationToken cancellation)
{
List<Func<CancellationToken, Task>> tmpInvocationList;
lock (locker)
{
tmpInvocationList = new List<Func<CancellationToken, Task>>(invocationList);
}
foreach (var callback in tmpInvocationList)
{
//Assuming we want a serial invocation, for a parallel invocation we can use Task.WhenAll instead
await callback(cancellation);
}
}
}
For my case worked a little tweak to the #grokky answer. I had to not run the event handlers in parallel (as pointed out by #Stephen Cleary) so i ran it in the for each loop fashion instead of going for the Task.WhenAll.
public delegate Task AsyncEventHandler(object sender, EventArgs e);
public abstract class DbContextBase:DbContext
{
public event AsyncEventHandler SavingChangesAsync;
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
await OnSavingChangesAsync(acceptAllChangesOnSuccess);
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private async Task OnSavingChangesAsync(bool acceptAllChangesOnSuccess)
{
if (SavingChangesAsync != null)
{
var asyncEventHandlers = SavingChangesAsync.GetInvocationList().Cast<AsyncEventHandler>();
foreach (AsyncEventHandler asyncEventHandler in asyncEventHandlers)
{
await asyncEventHandler.Invoke(this, new SavingChangesEventArgs(acceptAllChangesOnSuccess));
}
}
}
}

Task T.IsCancelled = false; How can I make this true?

I have this asynchronous method :
private static async Task Initializ( ) { /*Do Stuff Here*/ }
I want to be able to monitor the task that results from calling this function :
Task T = Class.Initialize( );
if (T.IsCancelled){ /*Do Stuff Here*/ }
I have in place a CancellationTokenSource.
How can I make T (or the function Initialize) utilize that sources token such that if it is cancelled, T.IsCancelled will be true?
EDIT
I do not know for certain but I think the answer to my question lies within using a TaskCompletionSource object. The answer given by Mike has lead me to this conclusion...
From the documentation
A Task will complete in the TaskStatus.Canceled state under any of
the following conditions:
Its CancellationToken was marked for cancellation before the task
started executing,
The task acknowledged the cancellation request on its already signaled
CancellationToken by throwing an OperationCanceledException that bears
the same CancellationToken.
The task acknowledged the cancellation request on its already signaled
CancellationToken by calling the ThrowIfCancellationRequested method
on the CancellationToken.
Updated:
Use this method:
async Task<Task> UntilCompletionOrCancellation(Task asyncOp, CancellationToken ct)
{
var tcs = new TaskCompletionSource<bool>();
using(ct.Register(() => tcs.TrySetResult(true)))
await Task.WhenAny(asyncOp, tcs.Task);
return asyncOp;
}
Consuming task:
var cts = new CancellationTokenSource();
await UntilCompletionOrCancellation(Class.Initialize, cts.Token);
if (!Class.Initialize.IsCompleted)
{
/*Do Stuff Here*/
}
Another approach is to remove async from Initialize
private static Task Initialize()
{
var tcs = new TaskCompletionSource();
//use TrySetResult or TrySetCancelled
return tcs.Task;
}
You can await this task and check whether is canceled or completed.
It is enough to just throw an OperationCanceledException from your asynchronous method.
The following writes true to the console:
public static void Main()
{
Console.WriteLine(DoSomethingAsync().IsCanceled);
}
private static async Task DoSomethingAsync()
{
throw new OperationCanceledException();
}
A nicer way to support cancellation is to have your asynchronous method take a CancellationToken as a parameter, it may then use this token to check for cancellation, e.g:
public static async Task DoSomethingAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
}
You need to call CancellationToken.ThrowIfCancellationRequested() inside the method and let the exception bubble up.
CancellationTokenSource cts = new CancellationTokenSource();
Task T = Class.Initialize(cts.Token);
if (T.IsCancelled){ /*Do Stuff Here*/ }
private static async Task Initializ(CancellationToken token )
{
/*Do Stuff Here*/
token.ThrowIfCancellationRequested();
/*Do More Stuff Here*/
}

Accepting incoming requests asynchronously

I have identified a bottleneck in my TCP application that I have simplified for the sake of this question.
I have a MyClient class, that represents when a client connects; also I have a MyWrapper class, that represents a client that fulfill some conditions. If a MyClientfulfill some conditions, it qualifies for wrapper.
I want to expose an method that allows the caller to await a MyWrapper, and that method should handle the negotiation and rejection of invalid MyClients:
public static async Task StartAccepting(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var wrapper = await AcceptWrapperAsync(token);
HandleWrapperAsync(wrapper);
}
}
Therefore AcceptWrapperAsync awaits a valid wrapper, and HandleWrapperAsync handles the wrapper asynchronously without blocking the thread, so AcceptWrapperAsync can get back to work as fast as it can.
How that method works internally is something like this:
public static async Task<MyWrapper> AcceptWrapperAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var client = await AcceptClientAsync();
if (IsClientWrappable(client))
return new MyWrapper(client);
}
return null;
}
public static async Task<MyClient> AcceptClientAsync()
{
await Task.Delay(1000);
return new MyClient();
}
private static Boolean IsClientWrappable(MyClient client)
{
Thread.Sleep(500);
return true;
}
This code simulates that there is a client connection every second, and that it takes half a second to checkout if the connection is suitable for a wrapper. AcceptWrapperAsync loops till a valid wrapper is generated, and then returns.
This approach, that works well, has a flaw. During the time that IsClientWrappable is executing, no further clients can be accepted, creating a bottleneck when lot of clients are trying to connect at the same time. I am afraid that in real life, if the server goes down while having lot of clients connected, the going up is not gonna be nice because all of them will try to connect at the same time. I know that is very difficult to connect all of them at the same time, but I would like to speed up the connection process.
Making IsClientWrappable async, would just ensure that the executing thread is not blocked till the negotiation finishes, but the execution flow is blocked anyway.
How could I improve this approach to continuously accept new clients but still be able of awaiting a wrapper using AcceptWrapperAsync?
//this loop must never be blocked
while (!token.IsCancellationRequested)
{
var client = await AcceptClientAsync();
HandleClientAsync(client); //must not block
}
Task HandleClientAsync(Client client) {
if (await IsClientWrappableAsync(client)) //make async as well, don't block
await HandleWrapperAsync(new MyWrapper(client));
}
This way you move the IsClientWrappable logic out of the accept loop and into the background async workflow.
If you do not wish to make IsClientWrappable non-blocking, just wrap it with Task.Run. It is essential that HandleClientAsync does not block so that its caller doesn't either.
TPL Dataflow to the rescue. I have created a "producer/consumer" object with two queues that:
accepts inputs from "producer" and stores it in the "in" queue.
a internal asynchronous task read from the "in" queue and process the input in parallel with a given maximum degree of parallelism.
put the processed item in the "out" queue afterwards. Result or Exception.
accepts a consumer to await an item. Then can check if the processing was successful or not.
I have done some testing and it seems to work fine, I want to do more testing though:
public sealed class ProcessingResult<TOut>
where TOut : class
{
public TOut Result { get; internal set; }
public Exception Error { get; internal set; }
}
public abstract class ProcessingBufferBlock<TIn,TOut>
where TIn:class
where TOut:class
{
readonly BufferBlock<TIn> _in;
readonly BufferBlock<ProcessingResult<TOut>> _out;
readonly CancellationToken _cancellation;
readonly SemaphoreSlim _semaphore;
public ProcessingBufferBlock(Int32 boundedCapacity, Int32 degreeOfParalellism, CancellationToken cancellation)
{
_cancellation = cancellation;
_semaphore = new SemaphoreSlim(degreeOfParalellism);
var options = new DataflowBlockOptions() { BoundedCapacity = boundedCapacity, CancellationToken = cancellation };
_in = new BufferBlock<TIn>(options);
_out = new BufferBlock<ProcessingResult<TOut>>(options);
StartReadingAsync();
}
private async Task StartReadingAsync()
{
await Task.Yield();
while (!_cancellation.IsCancellationRequested)
{
var incoming = await _in.ReceiveAsync(_cancellation);
ProcessThroughGateAsync(incoming);
}
}
private async Task ProcessThroughGateAsync(TIn input)
{
_semaphore.Wait(_cancellation);
Exception error=null;
TOut result=null;
try
{
result = await ProcessAsync(input);
}
catch (Exception ex)
{
error = ex;
}
finally
{
if(result!=null || error!=null)
_out.Post(new ProcessingResult<TOut>() { Error = error, Result = result });
_semaphore.Release(1);
}
}
protected abstract Task<TOut> ProcessAsync(TIn input);
public void Post(TIn item)
{
_in.Post(item);
}
public Task<ProcessingResult<TOut>> ReceiveAsync()
{
return _out.ReceiveAsync();
}
}
So the example I used on the OP would be something like this:
public class WrapperProcessingQueue : ProcessingBufferBlock<MyClient, MyWrapper>
{
public WrapperProcessingQueue(Int32 boundedCapacity, Int32 degreeOfParalellism, CancellationToken cancellation)
: base(boundedCapacity, degreeOfParalellism, cancellation)
{ }
protected override async Task<MyWrapper> ProcessAsync(MyClient input)
{
await Task.Delay(5000);
if (input.Id % 3 == 0)
return null;
return new MyWrapper(input);
}
}
And then I could add MyClient objects to that queue as fast as I get them, they would be processed in parallel, and the consumer would await for the ones that pass the filter.
As I said, I want to do more testing but any feedback will be very welcomed.
Cheers.

Categories

Resources