Since the API calls are not very CPU intensive (mostly waiting for web responses), Based on Servy's answer Servy's answer, I kept it simple by using async/await rather than using threads, but I may be wrong.
Since the API calls are not very CPU intensive (mostly waiting for web responses), Based on Servy's answer, I kept it simple by using async/await rather than using threads, but I may be wrong.
Since the API calls are not very CPU intensive (mostly waiting for web responses), Based on Servy's answer, I kept it simple by using async/await rather than using threads, but I may be wrong.
- 821
- 4
- 10
EDIT:
I added a cancellation token and a "task execution time" measurement to adjust the delayed release of the semaphore.
Note that I had to put the TaskUtilities.RetryOnFailure()
content inside the Enqueue T method for more convenience, but you might not find this really clean. I should certainly rewrite this in a separate method.
Side question: why externalized it to the TaskUtility
class rather than a private TaskQueue's method (since it's not used elsewhere) ?
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
// Acquire a new cancellation source if none or if the previous was cancelled.
if (source == null || source.IsCancellationRequested)
{
source = new CancellationTokenSource();
}
// Only one task can be running at one time.
await semaphore.WaitAsync(source.Token);
// Replacement of TaskUtilities.RetryOnFailure
DateTime dateStart = DateTime.Now;
try
{
int maxRetryCount = NumberOfRetries;
while (true)
{
try
{
return await taskGenerator();
}
catch(Exception ex)
{
if (maxRetryCount == 0)
{
// Cancel pending tasks.
if (CancelQueueOnFailure)
{
source.Cancel();
}
// Rethrow the current exception.
throw;
}
maxRetryCount--;
}
// Wait a bit if the task finished faster than the specified minimum duration.
TimeSpan delay = TimeBetweenTaskExecutions - (DateTime.Now - dateStart);
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay);
}
dateStart = DateTime.Now;
}
}
finally
{
ReleaseAfterDelay(TimeBetweenTaskExecutions - (DateTime.Now - dateStart));
}
}
/// <summary>
/// Release the semaphore after a given delay.
/// </summary>
private async void ReleaseAfterDelay(TimeSpan delay)
{
try
{
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
Also, for specific needs, I want to manually process the queue (when idle), after all tasks were enqueued. Would you recommend to create a separate class, or just use semaphore = new SemaphoreSlim(0, 1)
and semaphore.Release(1)
?
The first case allows me to instantiate the CancellationTokenSource
as I release the semaphore, making my previous code cleaner.
The second one could need a "pending tasks" counter to prevent multiple releasings, but that clearly denies your logic separation advice.
EDIT:
I added a cancellation token and a "task execution time" measurement to adjust the delayed release of the semaphore.
Note that I had to put the TaskUtilities.RetryOnFailure()
content inside the Enqueue T method for more convenience, but you might not find this really clean. I should certainly rewrite this in a separate method.
Side question: why externalized it to the TaskUtility
class rather than a private TaskQueue's method (since it's not used elsewhere) ?
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
// Acquire a new cancellation source if none or if the previous was cancelled.
if (source == null || source.IsCancellationRequested)
{
source = new CancellationTokenSource();
}
// Only one task can be running at one time.
await semaphore.WaitAsync(source.Token);
// Replacement of TaskUtilities.RetryOnFailure
DateTime dateStart = DateTime.Now;
try
{
int maxRetryCount = NumberOfRetries;
while (true)
{
try
{
return await taskGenerator();
}
catch(Exception ex)
{
if (maxRetryCount == 0)
{
// Cancel pending tasks.
if (CancelQueueOnFailure)
{
source.Cancel();
}
// Rethrow the current exception.
throw;
}
maxRetryCount--;
}
// Wait a bit if the task finished faster than the specified minimum duration.
TimeSpan delay = TimeBetweenTaskExecutions - (DateTime.Now - dateStart);
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay);
}
dateStart = DateTime.Now;
}
}
finally
{
ReleaseAfterDelay(TimeBetweenTaskExecutions - (DateTime.Now - dateStart));
}
}
/// <summary>
/// Release the semaphore after a given delay.
/// </summary>
private async void ReleaseAfterDelay(TimeSpan delay)
{
try
{
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
Also, for specific needs, I want to manually process the queue (when idle), after all tasks were enqueued. Would you recommend to create a separate class, or just use semaphore = new SemaphoreSlim(0, 1)
and semaphore.Release(1)
?
The first case allows me to instantiate the CancellationTokenSource
as I release the semaphore, making my previous code cleaner.
The second one could need a "pending tasks" counter to prevent multiple releasings, but that clearly denies your logic separation advice.
EDIT:
I added a cancellation token and a "task execution time" measurement to adjust the delayed release of the semaphore.
Note that I had to put the TaskUtilities.RetryOnFailure()
content inside the Enqueue T method for more convenience, but you might not find this really clean. I should certainly rewrite this in a separate method.
Side question: why externalized it to the TaskUtility
class rather than a private TaskQueue's method (since it's not used elsewhere) ?
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
// Acquire a new cancellation source if none or if the previous was cancelled.
if (source == null || source.IsCancellationRequested)
{
source = new CancellationTokenSource();
}
// Only one task can be running at one time.
await semaphore.WaitAsync(source.Token);
// Replacement of TaskUtilities.RetryOnFailure
DateTime dateStart = DateTime.Now;
try
{
int maxRetryCount = NumberOfRetries;
while (true)
{
try
{
return await taskGenerator();
}
catch(Exception ex)
{
if (maxRetryCount == 0)
{
// Cancel pending tasks.
if (CancelQueueOnFailure)
{
source.Cancel();
}
// Rethrow the current exception.
throw;
}
maxRetryCount--;
}
// Wait a bit if the task finished faster than the specified minimum duration.
TimeSpan delay = TimeBetweenTaskExecutions - (DateTime.Now - dateStart);
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay);
}
dateStart = DateTime.Now;
}
}
finally
{
ReleaseAfterDelay(TimeBetweenTaskExecutions - (DateTime.Now - dateStart));
}
}
/// <summary>
/// Release the semaphore after a given delay.
/// </summary>
private async void ReleaseAfterDelay(TimeSpan delay)
{
try
{
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
Also, for specific needs, I want to manually process the queue (when idle), after all tasks were enqueued. Would you recommend to create a separate class, or just use semaphore = new SemaphoreSlim(0, 1)
and semaphore.Release(1)
?
The first case allows me to instantiate the CancellationTokenSource
as I release the semaphore, making my previous code cleaner.
The second one could need a "pending tasks" counter to prevent multiple releasings, but that clearly denies your logic separation advice.
EDIT:
I added a cancellation token and a "task execution time" measurement to adjust the delayed release of the semaphore.
Note that I had to put the TaskUtilities.RetryOnFailure()
content inside the Enqueue T method for more convenience, but you might not find this really clean. I should certainly rewrite this in a separate method.
Side question: why externalized it to the TaskUtility
class rather than a private TaskQueue's method (since it's not used elsewhere) ?
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
// Acquire a new cancellation source if none or if the previous was cancelled.
if (source == null || source.IsCancellationRequested)
{
source = new CancellationTokenSource();
}
// Only one task can be running at one time.
await semaphore.WaitAsync(source.Token);
// Replacement of TaskUtilities.RetryOnFailure
DateTime dateStart = DateTime.Now;
try
{
int maxRetryCount = NumberOfRetries;
while (true)
{
try
{
return await taskGenerator();
}
catch(Exception ex)
{
if (maxRetryCount == 0)
{
// Cancel pending tasks.
if (CancelQueueOnFailure)
{
source.Cancel();
}
// Rethrow the current exception.
throw;
}
maxRetryCount--;
}
// Wait a bit if the task finished faster than the specified minimum duration.
TimeSpan delay = TimeBetweenTaskExecutions - (DateTime.Now - dateStart);
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay);
}
dateStart = DateTime.Now;
}
}
finally
{
ReleaseAfterDelay(TimeBetweenTaskExecutions - (DateTime.Now - dateStart));
}
}
/// <summary>
/// Release the semaphore after a given delay.
/// </summary>
private async void ReleaseAfterDelay(TimeSpan delay)
{
try
{
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
Also, for specific needs, I want to manually process the queue (when idle), after all tasks were enqueued. Would you recommend to create a separate class, or just use semaphore = new SemaphoreSlim(0, 1)
and semaphore.Release(1)
?
The first case allows me to instantiate the CancellationTokenSource
as I release the semaphore, making my previous code cleaner.
The second one could need a "pending tasks" counter to prevent multiple releasings, but that clearly denies your logic separation advice.