I have the following test code to simulate the use of a semaphore and throttling task execution. Is there a way to not continue to create new tasks if one of the running tasks throws an exception like the below. I don't need the existing tasks to stop running, I just want no new tasks to start after the exception is encountered.
Currently the tasks will all start in this scenario below. I want it to stop after a few tasks are ran because of the exceptions being thrown.
var testStrings = new List<string>();
for (var i = 0; i < 5000; i++)
{
testStrings.Add($"string-{i}");
}
using (var semaphore = new SemaphoreSlim(10))
{
var tasks = testStrings.Select(async testString =>
{
await semaphore.WaitAsync();
try
{
Console.WriteLine($"{testString}-Start");
await Task.Delay(2000);
throw new Exception("test");
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
As per Sinatr's comments, I think this may work for me by adding a cancellation token to be monitored.
var testStrings = new List<string>();
for (var i = 0; i < 5000; i++)
{
testStrings.Add($"string-{i}");
}
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
using (var semaphore = new SemaphoreSlim(10))
{
var tasks = testStrings.Select(async testString =>
{
await semaphore.WaitAsync();
try
{
if (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"{testString}-Start");
await Task.Delay(2000);
throw new Exception("test");
}
}
catch (Exception ex)
{
if (!cancellationTokenSource.IsCancellationRequested)
cancellationTokenSource.Cancel();
throw;
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
Related
I'm a bit new to using Semaphores, and I'm a bit confused by this. All I'm doing is grabbing the html code from a page, and writing it to a file, over and over. When I use it in a foreach, it works perfectly. When I change it over to a for loop, I get a the file is used by another process exception, and it references the file that is after the max count. What is the difference between these two? They should be processing the same files.
Works perfectly:
private async Task GetFiles()
{
string strDir = ConfigurationManager.AppSettings["location"];
var maxThreads = 4;
var allTasks = new List<Task>();
SemaphoreSlim throttler = new SemaphoreSlim(initialCount: maxThreads);
foreach(FileToProcess ftpFile in lstFiles)
{
await throttler.WaitAsync();
allTasks.Add(
Task.Run(async () =>
{
try
{
await GetHtmlFromUrlAsync(ftpFile, strDir);
}
finally
{
throttler.Release();
}
}));
}
await Task.WhenAll(allTasks);
}
Gets an error after on the 5th item in the list (max thread + 1). If I adjusted the max thread count, the error would change as well:
private async Task GetFiles()
{
string strDir = ConfigurationManager.AppSettings["location"];
var maxThreads = 4;
var allTasks = new List<Task>();
SemaphoreSlim throttler = new SemaphoreSlim(initialCount: maxThreads);
for (int x = 0; x < lstFiles.Count; x++)
{
await throttler.WaitAsync();
allTasks.Add(
Task.Run(async () =>
{
try
{
await GetHtmlFromUrlAsync(lstFiles[x], strDir);
}
finally
{
throttler.Release();
}
}));
}
await Task.WhenAll(allTasks);
}
In the WPF .net core app there is the following:
An Observable Collection of items (itemObservCollection).
A static readonly HttpClient _httpclient
XML Responses
I am making a URL call to the api on each item in the observable collection (0 to 1000 items in collection). The return is XML. The XML is parsed using XElement. The property values in the observable collection are updated from the XML.
Task.Run is used to run the operation off the UI thread. Parallel.Foreach is used to make the calls in Parallel.
I feel I have made the solution overly complicated. Is there a way to simplify this? UpdateItems() is called from a button click.
private async Task UpdateItems()
{
try
{
await Task.Run(() => Parallel.ForEach(itemObservCollection, new ParallelOptions { MaxDegreeOfParallelism = 12 }, async item =>
{
try
{
var apiRequestString = $"http://localhost:6060/" + item.Name;
HttpResponseMessage httpResponseMessage = await _httpclient.GetAsync(apiRequestString);
var httpResponseStream = await httpResponseMessage.Content.ReadAsStreamAsync();
StringBuilder sb = new StringBuilder(1024);
XElement doc = XElement.Load(httpResponseStream);
foreach (var elem in doc.Descendants())
{
if (elem.Name == "ItemDetails")
{
var itemUpdate = itemObservCollection.FirstOrDefault(updateItem => updateItem.Name == item.Name);
if (itemUpdate != null)
{
itemUpdate.Price = decimal.Parse(elem.Attribute("Price").Value);
itemUpdate.Quantity = int.Parse(elem.Attribute("Quantity").Value);
}
}
}
}
catch (Exception ex)
{
LoggerTextBlock.Text = ('\n' + ex.ToString());
}
}));
}
catch (Exception ex)
{
LoggerTextBlock.Text = ('\n' + ex.ToString());
}
}
You could create an array of tasks and await them all using Task.WhenAll.
The following sample code kicks off a task per item in the ObservableCollection<int> and then wait asynchronously for all tasks to finish:
ObservableCollection<int> itemObservCollection =
new ObservableCollection<int>(Enumerable.Range(1, 10));
async Task SendAsync()
{
//query the HTTP API here...
await Task.Delay(1000);
}
await Task.WhenAll(itemObservCollection.Select(x => SendAsync()).ToArray());
If you want to limit the number of concurrent requests, you could either iterate through a subset of the source collecton to send requests in batches or use a SemaphoreSlim to limit the number of actual concurrent requests:
Task[] tasks = new Task[itemObservCollection.Count];
using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(12))
{
for (int i = 0; i < itemObservCollection.Count; ++i)
{
async Task SendAsync()
{
//query the HTTP API here...
try
{
await Task.Delay(5000);
}
finally
{
semaphoreSlim.Release();
}
}
await semaphoreSlim.WaitAsync();
tasks[i] = SendAsync();
}
await Task.WhenAll(tasks);
}
I need to run multiple task to access to database and to wait to all task finished to obtain the result and assign it to my viewmodel, i try many sample but i have never have the result, any help please, this my code
var model = new AggregationViewModel();
var loadDataTasks = new Task[]
{
Task.Run(async () =>model.Alive = await _repository.GetCountAsync<filter>(Predicate)),
Task.Run(async () => model.Delay = await _repository.GetCountAsync<Flight>(predicate.And(x => x.Status == "refused")))
};
try
{
await Task.WhenAll(loadDataTasks);
foreach (var item in loadDataTasks)
{
}
}
catch (Exception ex)
{
}
Check this out:
var task1 = _repository.GetCountAsync<filter>(Predicate);
var task2 = _repository.GetCountAsync<Flight>(predicate.And(x => x.Status == "refused"));
await Task.WhenAll(task1, task2); //wait both tasks to finish
model.Alive = await task1;
model.Delay = await task2;
PS: the function, which the above code is placed, is needed to be async for the above scenario.
Try this code:
var model = new AggregationViewModel();
//----------------------------------------
var tasks = new List<Task<int>>();
//Add CountA
tasks.Add(_repository.GetCountAsync<filter>(Predicate));
//Add CountB
tasks.Add(_repository.GetCountAsync<Flight>(predicate.And(x => x.Status == "refused")));
//----------------------------------------
// Create a task with "allTasks" name to wait to complete all tasks
Task allTasks = Task.WhenAll(tasks);
try {
// Wait to compelete "allTask"
allTasks.Wait();
}
catch(AggregateException)
{}
//----------------------------------------
//Working on the results
if (allTasks.Status == TaskStatus.RanToCompletion) {
model.CountA=allTasks.Result[0]
model.CountB=allTasks.Result[1]
}
// Display information on faulted tasks.
else {
foreach (var t in tasks) {
Console.WriteLine("Task {0}: {1}", t.Id, t.Status);
}
}
My task: Organize the stability of the server under the load exceeding its capabilities.
Here is the code:
private async Task HandleOneRequest(HttpListenerContext listenerContext)
{
// one request processing
await Task.CompletedTask;
}
private async Task HandleContextAsync(HttpListenerContext listenerContext)
{
var allTasks = new List<Task>();
var queue = new Queue<HttpListenerContext>();
queue.Enqueue(listenerContext);
if (queue.Count > 50) // the number 50 is taken approximately
{
// many request
}
else
{
using (var throttler = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount))
{
foreach (var request in queue)
{
await throttler.WaitAsync();
allTasks.Add(Task.Run(async () =>
{
try
{
await HandleOneRequest(queue.Dequeue());
}
finally
{
throttler.Release();
}
}));
}
await Task.WhenAll(allTasks);
}
}
}
Am I using the semaphore correctly? How else can you implement throttling http-server? Is the request queue created correctly?
I have created a task which creates an XML string. The task can last multiple seconds. When the task isn't finished after 5 seconds, I want to cancel the Task 'smootly' and continue with writing the rest of the XML. So I built in cancellation inside my task. But although I see the following message in the Log:
ProcessInformationTask timed out
I also see this line in my log
Adding the process information took 10001 ms
I wonder why this can happen because I want to cancel the Task after 5 seconds (if not finished). So I expect the task to last 5 seconds max. How can I solve this? Probably the cancelation isn't setup properly?
Code where I call my task
string additionalInformation = null;
var contextInfo = new StringBuilder();
var xmlWriterSettings = new XmlWriterSettings()
{
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
using (XmlWriter xmlWriter = XmlWriter.Create(contextInfo, xmlWriterSettings))
{
try
{
xmlWriter.WriteStartElement("AdditionalInformation");
//Write xml (not long running)
var watch = System.Diagnostics.Stopwatch.StartNew();
string processInformation = AddProcessesInformation(xmlWriterSettings);
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
Log.Info("Adding the process information took : " + elapsedMs + " ms");
if (!string.IsNullOrEmpty(processInformation))
{
xmlWriter.WriteRaw(processInformation);
}
//Write xml (not long running)
xmlWriter.WriteEndElement();
additionalInformation = contextInfo.ToString();
}
catch (Exception e)
{
Log.Info("An exception occured during writing the additional information: " + e.Message);
return false;
}
return true;
}
Task method
private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var contextInfo = new StringBuilder();
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
if (token.IsCancellationRequested)
{
Log.Info("Cancellation request for the ProcessInformationTask");
}
Thread.Sleep(10000);
return "Ran without problems'";
}, token);
if (!processInformationTask.Wait(5000, token))
{
Log.Info("ProcessInformationTask timed out");
tokenSource.Cancel();
}
return processInformationTask?.Result;
}
I think you should check if cancellation is requested multiple Times, after each step in your method. Here is example using for loop:
private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var contextInfo = new StringBuilder();
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
for(int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation request for the ProcessInformationTask");
return string.Empty;
}
Thread.Sleep(1000);
}
return "Ran without problems'";
}, token);
if (!processInformationTask.Wait(5000, token))
{
Console.WriteLine("ProcessInformationTask timed out");
tokenSource.Cancel();
}
return processInformationTask?.Result;
}
If inside task method you call other methods, you can pass cancellation token to them and use method token.ThrowIfCancellationRequested() inside.
var processInformationTask = Task<string>.Factory.StartNew(() =>
{
try
{
Method1(token);
Thread.Sleep(1000);
Method2(token);
Thread.Sleep(1000);
}
catch(OperationCanceledException ex)
{
return string.Empty;
}
return "Ran without problems'";
}, token);
private static void Method1(CancellationToken token)
{
token.ThrowIfCancellationRequested();
// do more work
}