I am trying to create a helper class for sending some information periodically to backend server.
Attaching the code below.
public class HeartBeatService
{
private CancellationToken _cancellationToken;
private CancellationTokenSource _cancellationTokenSource;
public void StartHeartBeatService(TimeSpan timeSpan)
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
Task.Run(async () =>
{
while (!_cancellationToken.IsCancellationRequested)
{
SendHeartBeatToAzure();
try
{
await Task.Delay(timeSpan, _cancellationToken);
}
catch
{
break;
}
}
});
}
public void SuspendHeartBeatService()
{
_cancellationTokenSource?.Cancel();
}
private async void SendHeartBeatToAzure()
{
var platformService = ServiceLocator.Get<IPlatformService>();
var location = await platformService?.GetPositionAsync();
if (!double.IsNaN(location.Item1) && !double.IsNaN(location.Item2))
{
Debug.WriteLine($"Triggering Heartbeat with location{location.Item1},{location.Item2}");
//TODO Invoke heartbeat api call.
}
}
}
The code for sending the information to server is working fine.
But there is some issue with CancellationToken which is not working/it is not cancelling.
not sure what's wrong with the implementation.
Change the signature of the SendHeartBeatToAzure to return a Task, so that it can be awaited:
private async Task SendHeartBeatToAzure()
Then await the task returned by the method inside the loop. To achieve a stable and consisted heartbeat, it is a good idea to create the Task.Delay task before calling the method:
Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(timeSpan, _cancellationToken);
await SendHeartBeatToAzure();
await delayTask;
}
});
As a side note, you should probably store the task returned by Task.Run as a readonly property of the HeartBeatService class, so that the status of the task can be monitored.
Related
I have a class which creates a Task which runs during its whole lifetime, that is, until Dispose() is called on it:
In the constructor I call:
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
The way I currently do it (which I am also not sure is the right way) is cancelling the CancellationToken, and waiting on the task.
public void Dispose()
{
if (_isDisposed)
{
return;
}
_workerCancellation.Cancel();
_worker.GetAwaiter().GetResult();
_isDisposed = true;
}
When I do the same in the AsyncDispose method like so:
public async ValueTask DisposeAsync()
{
await _worker;
}
I get this warning
How do I correctly dispose of such a worker? Thanks!
As requested, here is the full code of what I am trying to do:
public sealed class ActiveObjectWrapper<T, TS> : IAsyncDisposable
{
private bool _isDisposed = false;
private const int DefaultQueueCapacity = 1024;
private readonly Task _worker;
private readonly CancellationTokenSource _workerCancellation;
private readonly Channel<(T, TaskCompletionSource<TS>)> _taskQueue;
private readonly Func<T, TS> _onReceive;
public ActiveObjectWrapper(Func<T, TS> onReceive, int? queueCapacity = null)
{
_onReceive = onReceive;
_taskQueue = Channel.CreateBounded<(T, TaskCompletionSource<TS>)>(queueCapacity ?? DefaultQueueCapacity);
_workerCancellation = new CancellationTokenSource();
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
await foreach (var (value, taskCompletionSource) in _taskQueue.Reader.ReadAllAsync(cancellationToken))
{
try
{
var result = _onReceive(value); // todo: do I need to propagate the cancellation token?
taskCompletionSource.SetResult(result);
}
catch (Exception exception)
{
taskCompletionSource.SetException(exception);
}
}
}
public async Task<TS> EnqueueAsync(T value)
{
// see: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
var completionSource = new TaskCompletionSource<TS>(TaskCreationOptions.RunContinuationsAsynchronously);
await _taskQueue.Writer.WriteAsync((value, completionSource));
return await completionSource.Task;
}
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_taskQueue.Writer.Complete();
_workerCancellation.Cancel();
await _worker;
_isDisposed = true;
}
}
This is the pattern that I use when implementing both IDisposabe and IDisposeableAsync. It isn't strictly compliant with the .Net recommendations. I found that implementing DisposeAsyncCore() was unnecessary as my classes are sealed.
public void Dispose()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
Worker.GetAwaiter().GetResult();
}
public void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
if (disposing)
{
lock (isDisposing)
{
if (isDisposed)
{
return;
}
Cts.Cancel();
Cts.Dispose();
isDisposed = true;
}
}
}
public async ValueTask DisposeAsync()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
await Worker;
}
This looks like an attempt to build a TransformBlock<TIn,TOut> on top of Channels. Using a Channel properly wouldn't generate such a warning. There's no reason to use a single processing task, or store it in a field.
Using Blocks
First, the equivalent code using a TransformBlock<Tin,TOut> would be:
var block=new TransformBlock<TIn,TOut>(msgIn=>func(msgIn));
foreach(....)
{
block.Post(someMessage);
}
To stop it, block.Complete() would be enough. Any pending messages would still be processed. A TransformBlock is meant to forward its output to other blocks, eg an ActionBlock or BufferBlock.
var finalBlock=new ActionBlock<TOut>(msgOut=>Console.WriteLine(msgOut));
block.LinkTo(finalBlock,new DataflowLinkOptions{PropagateCompletion = true});
...
block.Complete();
await finalBlock.Completion;
Using Channels
Doing something similar with channels doesn't need explicit classes, or Enqueue/Dequeue methods. Channels are build with separate ChannelReader, ChannelWriter interfaces to make it easier to control ownership, concurrency and completion.
A similar pipeline using channels would require only some methods:
ChannelReader<string> FolderToChannel(string path,CancellationToken token=default)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
foreach(var path in Directory.EnumerateFiles(path))
{
await _writer.SendAsync(path);
if (token.CancellationRequested)
{
return;
}
}
},token).ContinueWith(t=>_writer.TryComplete(t.Exception));
return channel;
}
This produces a reader that can be passed to a processing method. One that could generate another reader with the results:
static ChannelReader<MyClass> ParseFile(this ChannelReader<string> reader,CancellationToken token)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
await foreach(var path in reader.ReadAllAsync(token))
{
var json= await File.ReadAllTextAsync(path);
var dto= JsonSerializer.DeserializeObject<MyClass>(json);
await _writer.SendAsync(dto);
}
},token).ContineWith(t=>writer.TryComplete(t.Exception);
return channel;
}
And a final step that only consumes a channel:
static async Task LogIt(this ChannelReader<MyClass> reader,CancellationToken token)
{
await Task.Run(async ()=>{
await foreach(var dto in reader.ReadAllAsync(token))
{
Console.WriteLine(dto);
}
},token);
}
The three steps can be combined very easily:
var cts=new CancellationTokenSource();
var complete=FolderToChannel(somePath,cts.Token)
.ParseFile(cts.Token)
.LogIt(cts.Token);
await complete;
By encapsulating the channel itself and the processing in a method there's no ambiguity about who owns the channel, who is responsible for completion or cancellation
I have this metod:
public async Task StartAsync(Task process)
{
if (process is null)
{
throw new ArgumentNullException(nameof(process));
}
var loading = ...;
await Task.WhenAll(process, loading).ContinueWith(t => EndProgress());
}
and is called via a command like so:
private async Task TestAsync()
{
await StartAsync(new Task(async () =>
{
//just for testing purpose
await Task.Delay(15000);
}));
}
ExecuteDelegate = async param => await TestAsync();
where ExecuteDelegate is an Action<T> delegate used by command.
Why does the await Task.WhenAll line not waiting those 15 seconds from Task.Dalay?
You need to await the call to StartAsync:
private async Task TestAsync()
{
await StartAsync(new Task(async () =>
{
await Task.Delay(15000);
}));
}
NOTE: You can also simplify your code by not creating the redundant Task:
private async Task TestAsync()
{
await StartAsync(Task.Delay(15000));
}
Or even simpler:
private Task TestAsync()
{
return StartAsync(Task.Delay(15000));
}
You shouldn't use the constructor to create a Task but the static Task.Run method:
private async Task TestAsync()
{
await StartAsync(Task.Run(async () =>
{
//just for testing purpose
await Task.Delay(15000);
}));
}
The task returned by Task.Run can be awaited as expected.
My assertion of acceptor.IsStarted.Should().BeTrue(); (see unit test below) always fails, as it's getting evaluated too early. The call to await task returns immediately and doesn't give this.acceptor.Start() enough time to spin up.
I would like to make the startup of my FixAcceptor() more deterministic and therefor introduced the parameter TimeSpan startupDelay.
However I simply have no clue where and how I can delay the startup.
Putting an additional Thread.Sleep(startupDelay) between this.acceptor.Start() and this.IsStarted = true won't help as it will only block the worker task itself, but not the calling thread.
I hope it's clear what I'd like to archive and what I am struggling with. Thanks in advance.
public class FixAcceptor
{
// Type provided by QuickFix.net
private readonly ThreadedSocketAcceptor acceptor;
public FixAcceptor(IFixSettings settings)
{
// Shortened
}
public bool IsStarted { get; private set; }
public async void Run(CancellationToken cancellationToken, TimeSpan startupDelay)
{
var task = Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
this.acceptor.Start();
this.IsStarted = true;
while (true)
{
// Stop if token has been canceled
if (cancellationToken.IsCancellationRequested)
{
this.acceptor.Stop();
this.IsStarted = false;
cancellationToken.ThrowIfCancellationRequested();
}
// Save some CPU cycles
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}, cancellationToken);
try
{
await task;
}
catch (OperationCanceledException e)
{
Debug.WriteLine(e.Message);
}
}
}
And the corresponding consumer code
[Fact]
public void Should_Run_Acceptor_And_Stop_By_CancelationToken()
{
// Arrange
var acceptor = new FixAcceptor(new FixAcceptorSettings("test_acceptor.cfg", this.logger));
var tokenSource = new CancellationTokenSource();
// Act
tokenSource.CancelAfter(TimeSpan.FromSeconds(10));
acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));
// Assert
acceptor.IsStarted.Should().BeTrue();
IsListeningOnTcpPort(9823).Should().BeTrue();
// Wait for cancel event to occur
Thread.Sleep(TimeSpan.FromSeconds(15));
acceptor.IsStarted.Should().BeFalse();
}
Adding time delays to achieve determinism is not a recommended practice. You can achieve 100% determinism by using a TaskCompletionSource for controlling the completion of a task at just the right moment:
public Task<bool> Start(CancellationToken cancellationToken)
{
var startTcs = new TaskCompletionSource<bool>();
var task = Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
this.acceptor.Start();
this.IsStarted = true;
startTcs.TrySetResult(true); // Signal that the starting phase is completed
while (true)
{
// ...
}
}, cancellationToken);
HandleTaskCompletion();
return startTcs.Task;
async void HandleTaskCompletion() // async void method = should never throw
{
try
{
await task;
}
catch (OperationCanceledException ex)
{
Debug.WriteLine(ex.Message);
startTcs.TrySetResult(false); // Signal that start failed
}
catch
{
startTcs.TrySetResult(false); // Signal that start failed
}
}
}
Then replace this line in your test:
acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));
...with this one:
bool startResult = await acceptor.Start(tokenSource.Token);
Another issue that caught my eye is the bool IsStarted property which is mutated from one thread and observed by another, without synchronization. This is not really a problem because you could rely on the undocumented memory barrier that is inserted automatically on every await, and be quite confident that you'll not have visibility issues, but if you want to be extra sure you could synchronize the access by using a lock (most robust), or backup the property with a volatile private field like this:
private volatile bool _isStarted;
public bool IsStarted => _isStarted;
I would recommend that you structure your FixAcceptor.Run() methode a little bit different
public async Task Run(CancellationToken cancellationToken, TimeSpan startupDelay)
{
var task = Task.Run(async () =>
{
try
{
cancellationToken.ThrowIfCancellationRequested();
this.acceptor.Start();
this.IsStarted = true;
while (true)
{
// Stop if token has been canceled
if (cancellationToken.IsCancellationRequested)
{
this.acceptor.Stop();
this.IsStarted = false;
cancellationToken.ThrowIfCancellationRequested();
}
// Save some CPU cycles
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
catch (OperationCanceledException e)
{
Debut.WriteLine(e.Message);
}
}, cancellationToken);
await Task.Delay(startupDelay);
}
so the exception handling is in the inner task and the Run methode returns a Task that completes after the startupDelay.
(I also exchanged the Thread.Sleep() with a Task.Delay())
Then in the test methode you can await the Task returned by Run
[Fact]
public async Task Should_Run_Acceptor_And_Stop_By_CancelationToken()
{
// Arrange
var acceptor = new FixAcceptor(new FixAcceptorSettings("test_acceptor.cfg", this.logger));
var tokenSource = new CancellationTokenSource();
// Act
tokenSource.CancelAfter(TimeSpan.FromSeconds(10));
await acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));
// Assert
acceptor.IsStarted.Should().BeTrue();
IsListeningOnTcpPort(9823).Should().BeTrue();
// Wait for cancel event to occur
Thread.Sleep(TimeSpan.FromSeconds(15));
acceptor.IsStarted.Should().BeFalse();
}
It should be okay to make the mehtode async (it seams like you use xunit)
I created a small wrapper around CancellationToken and CancellationTokenSource. The problem I have is that the CancelAsync method of CancellationHelper doesn't work as expected.
I'm experiencing the problem with the ItShouldThrowAExceptionButStallsInstead method. To cancel the running task, it calls await coordinator.CancelAsync();, but the task is not cancelled actually and doesn't throw an exception on task.Wait
ItWorksWellAndThrowsException seems to be working well and it uses coordinator.Cancel, which is not an async method at all.
The question why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?
Don't let the waitHandle confuse you, it's only for not letting the task finish early.
Let the code speak for itself:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace TestCancellation
{
class Program
{
static void Main(string[] args)
{
ItWorksWellAndThrowsException();
//ItShouldThrowAExceptionButStallsInstead();
}
private static void ItShouldThrowAExceptionButStallsInstead()
{
Task.Run(async () =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() =>
{
waitHandle.WaitOne();
//this works well though - it throws
//coordinator.ThrowIfCancellationRequested();
}, coordinator.Token);
await coordinator.CancelAsync();
//waitHandle.Set(); -- with or without this it will throw
task.Wait();
}).Wait();
}
private static void ItWorksWellAndThrowsException()
{
Task.Run(() =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() => { waitHandle.WaitOne(); }, coordinator.Token);
coordinator.Cancel();
task.Wait();
}).Wait();
}
}
public class CancellationHelper
{
private CancellationTokenSource cancellationTokenSource;
private readonly List<Task> tasksToAwait;
public CancellationHelper()
{
cancellationTokenSource = new CancellationTokenSource();
tasksToAwait = new List<Task>();
}
public CancellationToken Token
{
get { return cancellationTokenSource.Token; }
}
public void AwaitOnCancellation(Task task)
{
if (task == null) return;
tasksToAwait.Add(task);
}
public void Reset()
{
tasksToAwait.Clear();
cancellationTokenSource = new CancellationTokenSource();
}
public void ThrowIfCancellationRequested()
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
public void Cancel()
{
cancellationTokenSource.Cancel();
Task.WaitAll(tasksToAwait.ToArray());
}
public async Task CancelAsync()
{
cancellationTokenSource.Cancel();
try
{
await Task.WhenAll(tasksToAwait.ToArray());
}
catch (AggregateException ex)
{
ex.Handle(p => p is OperationCanceledException);
}
}
}
}
Cancellation in .NET is cooperative.
That means that the one holding the CancellationTokenSource signals cancellation and the one holding the CancellationToken needs to check whether cancellation was signaled (either by polling the CancellationToken or by registering a delegate to run when it is signaled).
In your Task.Run you use the CancellationToken as a parameter, but you don't check it inside the task itself so the task will only be cancelled if the token was signaled before the task had to a chance to start.
To cancel the task while it's running you need to check the CancellationToken:
var task = Task.Run(() =>
{
token.ThrowIfCancellationRequested();
}, token);
In your case you block on a ManualResetEvent so you wouldn't be able to check the CancellationToken. You can register a delegate to the CancellationToken that frees up the reset event:
token.Register(() => waitHandle.Set())
I need to implement a library to request vk.com API. The problem is that API supports only 3 requests per second. I would like to have API asynchronous.
Important: API should support safe accessing from multiple threads.
My idea is implement some class called throttler which allow no more than 3 request/second and delay other request.
The interface is next:
public interface IThrottler : IDisposable
{
Task<TResult> Throttle<TResult>(Func<Task<TResult>> task);
}
The usage is like
var audio = await throttler.Throttle(() => api.MyAudio());
var messages = await throttler.Throttle(() => api.ReadMessages());
var audioLyrics = await throttler.Throttle(() => api.AudioLyrics(audioId));
/// Here should be delay because 3 requests executed
var photo = await throttler.Throttle(() => api.MyPhoto());
How to implement throttler?
Currently I implemented it as queue which is processed by background thread.
public Task<TResult> Throttle<TResult>(Func<Task<TResult>> task)
{
/// TaskRequest has method Run() to run task
/// TaskRequest uses TaskCompletionSource to provide new task
/// which is resolved when queue processed til this element.
var request = new TaskRequest<TResult>(task);
requestQueue.Enqueue(request);
return request.ResultTask;
}
This is shorten code of background thread loop which process the queue:
private void ProcessQueue(object state)
{
while (true)
{
IRequest request;
while (requestQueue.TryDequeue(out request))
{
/// Delay method calculates actual delay value and calls Thread.Sleep()
Delay();
request.Run();
}
}
}
Is it possible to implement this without background thread?
So we'll start out with a solution to a simpler problem, that of creating a queue that process up to N tasks concurrently, rather than throttling to N tasks started per second, and build on that:
public class TaskQueue
{
private SemaphoreSlim semaphore;
public TaskQueue()
{
semaphore = new SemaphoreSlim(1);
}
public TaskQueue(int concurrentRequests)
{
semaphore = new SemaphoreSlim(concurrentRequests);
}
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
await semaphore.WaitAsync();
try
{
return await taskGenerator();
}
finally
{
semaphore.Release();
}
}
public async Task Enqueue(Func<Task> taskGenerator)
{
await semaphore.WaitAsync();
try
{
await taskGenerator();
}
finally
{
semaphore.Release();
}
}
}
We'll also use the following helper methods to match the result of a TaskCompletionSource to a `Task:
public static void Match<T>(this TaskCompletionSource<T> tcs, Task<T> task)
{
task.ContinueWith(t =>
{
switch (t.Status)
{
case TaskStatus.Canceled:
tcs.SetCanceled();
break;
case TaskStatus.Faulted:
tcs.SetException(t.Exception.InnerExceptions);
break;
case TaskStatus.RanToCompletion:
tcs.SetResult(t.Result);
break;
}
});
}
public static void Match<T>(this TaskCompletionSource<T> tcs, Task task)
{
Match(tcs, task.ContinueWith(t => default(T)));
}
Now for our actual solution what we can do is each time we need to perform a throttled operation we create a TaskCompletionSource, and then go into our TaskQueue and add an item that starts the task, matches the TCS to its result, doesn't await it, and then delays the task queue for 1 second. The task queue will then not allow a task to start until there are no longer N tasks started in the past second, while the result of the operation itself is the same as the create Task:
public class Throttler
{
private TaskQueue queue;
public Throttler(int requestsPerSecond)
{
queue = new TaskQueue(requestsPerSecond);
}
public Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
var unused = queue.Enqueue(() =>
{
tcs.Match(taskGenerator());
return Task.Delay(TimeSpan.FromSeconds(1));
});
return tcs.Task;
}
public Task Enqueue<T>(Func<Task> taskGenerator)
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
var unused = queue.Enqueue(() =>
{
tcs.Match(taskGenerator());
return Task.Delay(TimeSpan.FromSeconds(1));
});
return tcs.Task;
}
}
I solved a similar problem using a wrapper around SemaphoreSlim. In my scenario, I had some other throttling mechanisms as well, and I needed to make sure that requests didn't hit the external API too often even if request number 1 took longer to reach the API than request number 3. My solution was to use a wrapper around SemaphoreSlim that had to be released by the caller, but the actual SemaphoreSlim would not be released until a set time had passed.
public class TimeGatedSemaphore
{
private readonly SemaphoreSlim semaphore;
public TimeGatedSemaphore(int maxRequest, TimeSpan minimumHoldTime)
{
semaphore = new SemaphoreSlim(maxRequest);
MinimumHoldTime = minimumHoldTime;
}
public TimeSpan MinimumHoldTime { get; }
public async Task<IDisposable> WaitAsync()
{
await semaphore.WaitAsync();
return new InternalReleaser(semaphore, Task.Delay(MinimumHoldTime));
}
private class InternalReleaser : IDisposable
{
private readonly SemaphoreSlim semaphoreToRelease;
private readonly Task notBeforeTask;
public InternalReleaser(SemaphoreSlim semaphoreSlim, Task dependantTask)
{
semaphoreToRelease = semaphoreSlim;
notBeforeTask = dependantTask;
}
public void Dispose()
{
notBeforeTask.ContinueWith(_ => semaphoreToRelease.Release());
}
}
}
Example usage:
private TimeGatedSemaphore requestThrottler = new TimeGatedSemaphore(3, TimeSpan.FromSeconds(1));
public async Task<T> MyRequestSenderHelper(string endpoint)
{
using (await requestThrottler.WaitAsync())
return await SendRequestToAPI(endpoint);
}
Here is one solution that uses a Stopwatch:
public class Throttler : IThrottler
{
private readonly Stopwatch m_Stopwatch;
private int m_NumberOfRequestsInLastSecond;
private readonly int m_MaxNumberOfRequestsPerSecond;
public Throttler(int max_number_of_requests_per_second)
{
m_MaxNumberOfRequestsPerSecond = max_number_of_requests_per_second;
m_Stopwatch = Stopwatch.StartNew();
}
public async Task<TResult> Throttle<TResult>(Func<Task<TResult>> task)
{
var elapsed = m_Stopwatch.Elapsed;
if (elapsed > TimeSpan.FromSeconds(1))
{
m_NumberOfRequestsInLastSecond = 1;
m_Stopwatch.Restart();
return await task();
}
if (m_NumberOfRequestsInLastSecond >= m_MaxNumberOfRequestsPerSecond)
{
TimeSpan time_to_wait = TimeSpan.FromSeconds(1) - elapsed;
await Task.Delay(time_to_wait);
m_NumberOfRequestsInLastSecond = 1;
m_Stopwatch.Restart();
return await task();
}
m_NumberOfRequestsInLastSecond++;
return await task();
}
}
Here is how this code can be tested:
class Program
{
static void Main(string[] args)
{
DoIt();
Console.ReadLine();
}
static async Task DoIt()
{
Func<Task<int>> func = async () =>
{
await Task.Delay(100);
return 1;
};
Throttler throttler = new Throttler(3);
for (int i = 0; i < 10; i++)
{
var result = await throttler.Throttle(func);
Console.WriteLine(DateTime.Now);
}
}
}
You can use this as Generic
public TaskThrottle(int maxTasksToRunInParallel)
{
_semaphore = new SemaphoreSlim(maxTasksToRunInParallel);
}
public void TaskThrottler<T>(IEnumerable<Task<T>> tasks, int timeoutInMilliseconds, CancellationToken cancellationToken = default(CancellationToken)) where T : class
{
// Get Tasks as List
var taskList = tasks as IList<Task<T>> ?? tasks.ToList();
var postTasks = new List<Task<int>>();
// When the first task completed, it will flag
taskList.ForEach(x =>
{
postTasks.Add(x.ContinueWith(y => _semaphore.Release(), cancellationToken));
});
taskList.ForEach(x =>
{
// Wait for open slot
_semaphore.Wait(timeoutInMilliseconds, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
x.Start();
});
Task.WaitAll(taskList.ToArray(), cancellationToken);
}
Edit: this solution works but use it only if it is ok to process all request in serial (in one thread). Otherwise use solution accepted as answer.
Well, thanks to Best way in .NET to manage queue of tasks on a separate (single) thread
My question is almost duplicate except adding delay before execution, which is actually simple.
The main helper here is SemaphoreSlim class which allows to restrict degree of parallelism.
So, first create a semaphore:
// Semaphore allows run 1 thread concurrently.
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
And, final version of throttle looks like
public async Task<TResult> Throttle<TResult>(Func<Task<TResult>> task)
{
await semaphore.WaitAsync();
try
{
await delaySource.Delay();
return await task();
}
finally
{
semaphore.Release();
}
}
Delay source is also pretty simple:
private class TaskDelaySource
{
private readonly int maxTasks;
private readonly TimeSpan inInterval;
private readonly Queue<long> ticks = new Queue<long>();
public TaskDelaySource(int maxTasks, TimeSpan inInterval)
{
this.maxTasks = maxTasks;
this.inInterval = inInterval;
}
public async Task Delay()
{
// We will measure time of last maxTasks tasks.
while (ticks.Count > maxTasks)
ticks.Dequeue();
if (ticks.Any())
{
var now = DateTime.UtcNow.Ticks;
var lastTick = ticks.First();
// Calculate interval between last maxTasks task and current time
var intervalSinceLastTask = TimeSpan.FromTicks(now - lastTick);
if (intervalSinceLastTask < inInterval)
await Task.Delay((int)(inInterval - intervalSinceLastTask).TotalMilliseconds);
}
ticks.Enqueue(DateTime.UtcNow.Ticks);
}
}