I'd like to improve the following code to add cancellation support. Basically, what I need to do is cancel all children as well as the parent task once a child throws an exception. I wrote the below code as a learning experience. I can see AggregateException only after all children finish, but I don't want that.
static int GetSum()
{
var parent = Task<int>.Factory.StartNew(() =>
{
var children = new Task<int>[100];
for (var i = 0; i < children.Length; i++)
{
var index = i;
children[index] = Task<int>.Factory.StartNew(() =>
{
var randomNumber = new Random().Next(5);
if (randomNumber == 0)
{
throw new Exception();
}
return randomNumber;
}, TaskCreationOptions.AttachedToParent);
}
Task.WaitAll();
Console.WriteLine("Children finished");
return children.Sum(t => t.Result);
});
parent.Wait();
Console.WriteLine("Parent finished");
return parent.Result;
}
I believe I need to use the following though I don't know how:
var source = new CancellationTokenSource();
var token = source.Token;
you can just use Task.WaitAny instead of WaitAll and make a cancel request to the token once an AgregateException was thrown something like this
static int GetSum()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var parent = Task<int>.Factory.StartNew(() =>
{
var children = new Task<int>[100];
for (var i = 0; i < children.Length; i++)
{
var index = i;
children[index] = Task<int>.Factory.StartNew(() =>
{
for (int j = 0; j < 100000; j++)
{
if (!token.IsCancellationRequested)
{
var randomNumber = new Random().Next(5);
if (randomNumber == 0)
{
throw new Exception();
}
return randomNumber;
}
else
{
token.ThrowIfCancellationRequested();
}
}
return 0;
}
, token);
}
try
{
Task.WaitAny(children);
}
catch (AggregateException ae)
{
tokenSource.Cancel();
ae.Handle((task) =>
{
Console.WriteLine("Cancel all others child tasks requested ");
return true;
});
}
Console.WriteLine("Children finished");
return children.Sum(t => t.Result);
});
try
{
parent.Wait();
}
catch (AggregateException aex)
{
aex.Handle((task) =>
{
Console.WriteLine("Cancel child work done ");
return true;
});
}
Console.WriteLine("Parent finished");
return parent.Result;
}
Related
I'm running a foreach and I would like to cancel its execution on a key press.
While I succeeded doing it integrating a single if (keypress) within the loop, now I'm trying to achieve the same using a CancellationToken while the task for listening for a key stroke is running.
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
while (true)
{
foreach (var station in stations)
{
/*if (Console.KeyAvailable)
{
break;
}*/
Console.WriteLine(station.name + " ");
Thread.Sleep(100);
}
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
// another thread decided to cancel
Console.WriteLine("task canceled");
break;
}
}
}, ct);
ts.Cancel();
Console.ReadLine();
I came from this answer How do I abort/cancel TPL Tasks? which helped me a lot.
However, while it works without the foreach, right now the foreach has to end before the task is cancelled.
Obviously, it looks like the iteration has to end before proceeding to the next step and what I don't understand is how I can make the foreach stop.
Cancellation is co-operative.
You need to check inside the foreach() and at any other appropriate point in your routines, to see if cancellation has been requested.
If it has, then you could prematurely exit out of the foreach(), (if your logic allows it), while ensuring that you clean up any resources and complete any actions that need to be completed.
Also, I suggest, instead of Thread.Sleep(), use await Task.Delay(100);.
It seems as though it's as simple as repeating your if:
var ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
Task.Factory.StartNew(() =>
{
while (true)
{
foreach (var station in stations)
{
/*if (Console.KeyAvailable)
{
break;
}*/
if (ct.IsCancellationRequested)
{
// another thread decided to cancel
break;
}
Console.WriteLine(station.name + " ");
Thread.Sleep(100);
}
Thread.Sleep(100);
if (ct.IsCancellationRequested)
{
// another thread decided to cancel
Console.WriteLine("task canceled");
break;
}
}
}, ct);
ts.Cancel();
Console.ReadLine();
As I commented before, I think you're trying to achieve something like this:
public class Program
{
static ConcurrentQueue<int> queue = new(); // >= .NET 5 / C# 9 only
public static void Main(string[] args)
{
var ts = new CancellationTokenSource();
var rand = new Random();
Task activeTask = null;
while (true)
{
var keyPressed = Console.ReadKey(true).Key;
if (keyPressed == ConsoleKey.Q)
{
for (int i = 0; i < 1000; i++)
queue.Enqueue(rand.Next(0, 1000));
Console.WriteLine("Random elements enqueued");
}
if (keyPressed == ConsoleKey.D)
{
if(activeTask == null)
{
ts.Dispose();
ts = new CancellationTokenSource();
CancellationToken ct = ts.Token;
activeTask = BackgroundLoopAction(ct);
}
else
Console.WriteLine("Background loop task already running");
}
if (keyPressed == ConsoleKey.X)
{
ts.Cancel();
activeTask = null;
}
if (keyPressed == ConsoleKey.Escape)
break;
}
}
private static Task BackgroundLoopAction(CancellationToken ct)
{
return Task.Run(() =>
{
while (queue.Count > 0)
{
queue.TryDequeue(out int q);
Console.WriteLine($"Dequeued element: {q}");
Thread.Sleep(100);
if (ct.IsCancellationRequested)
break;
}
Console.WriteLine(ct.IsCancellationRequested ? "Task canceled by user event" : "Task completed");
}, ct);
}
}
I use 'Task.Factory.ContinueWhenAll()' to improve app's performance,but i got a problem that the memory is slowly getting higher.
Then i use the GC.collect() and GC.GC.WaitForPendingFinalizers() and i got the same situation.
int taskCount = Settings.Default.TaskCount;
int monitorInterval = Settings.Default.MonitorInterval;
while (true)
{
using (TaskHandleSvr taskItem = new TaskHandleSvr())
{
List<Task> taskList = new List<Task>();
for (int i = 0; i < taskCount; i++)
taskList.Add(taskItem.RegisterRun());
await Task.Factory.ContinueWhenAll(taskList.ToArray(), wordCountTasks =>
{
int count = taskItem.ProcessMisseHandledRedisMsg();
string sLogTime = $"{DateTime.UtcNow.AddHours(8).ToString("yyyy-MM-dd HH:mm:ss.ffff")}";
string sLogName = "-";
string sLogMessage = "Service End!";
NLogHelper.Info($"{sLogName}*|*{sLogTime}*|*Loop{count}Msg");
NLogHelper.Info($"{sLogName}*|*{sLogTime}*|*{sLogMessage}");
});
taskItem.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
}
//Console.ReadKey();
await Task.Delay(TimeSpan.FromSeconds(5));
}
//Below is the TaskHandleSvr
Here i am ! Finally, i got my bug fixed. Whoa~
use 'static' task
there is a wrong place at "taskList.Add(taskItem.RegisterRun())", it should be 'taskList.Add(Task.Factory.StartNew(fn =>{TaskHandleSvr.RegisterRun().Wait(); }, null))'. After i printed threadid in 'RegisterRun()', i found that the current thread managed id is the same number. Then i realized there is something wrong with using multiple thread.
In case of not realsing memory in tasks, i use 'Task.Dispose()'
Thanks the Comments Upstairs, sincerely thank U~
Below is the Code
protected override async Task RunAsync(CancellationToken cancellationToken)
{
int taskCount= Settings.Default.TaskCount;
int monitorIntervalSeconds = Settings.Default.MonitorInterval;
List<Task> taskList = new List<Task>();
while (true)
{
for (int i = 0; i < taskCount; i++)
taskList.Add(Task.Factory.StartNew(fn => { TaskHandleSvr.RegisterRun().Wait(); }, null));
await Task.Factory.ContinueWhenAll(taskList.ToArray(), wordCountTasks =>
{
int count = TaskHandleSvr.ProcessMisseHandledRedisMsg().Result;
string sLogTime = $"{DateTime.UtcNow.AddHours(8).ToString("yyyy-MM-dd HH:mm:ss.ffff")}";
string sLogName = "--myTaskGroup--";
string sLogMessage = "Task End";
NLogHelper.Info($"{sLogName}*|*{sLogTime}*|*RollBack {count} Message");
NLogHelper.Info($"{sLogName}*|*{sLogTime}*|*{sLogMessage}");
});
//Release Resource
foreach (var item in taskList)
item.Dispose();
taskList.RemoveRange(0, taskList.Count);
await Task.Delay(TimeSpan.FromSeconds(monitorIntervalSeconds), cancellationToken);
}
}
So what i am trying to do here is:
Make the engine loop and work on an object if the queue is not empty.
If the queue is empty i call the manualresetevent to make the thread sleep.
When a item is added and the loop is not active i set the manualresetevent.
To make it faster i pick up atmost 5 items from the list and perform operation on them asynchronously and wait for all of them to finish.
Problem:
The clear methods on the two lists are called as soon as a new call to the AddToUpdateQueueMethod is called.
In my head as i am waiting for Task.WhenAll(tasks), so thread should wait for its completion before moving ahead, hence the clear on the lists should only be called on after Task.WhenAll(tasks) returns.
What am i missing here, or what will be a better way to achieve this.
public async Task ThumbnailUpdaterEngine()
{
int count;
List<Task<bool>> tasks = new List<Task<bool>>();
List<Content> candidateContents = new List<Content>();
while (true)
{
for (int i = 0; i < 5; i++)
{
Content nextContent = GetNextFromInternalQueue();
if (nextContent == null)
break;
else
candidateContents.Add(nextContent);
}
foreach (var candidateContent in candidateContents)
{
foreach (var provider in interactionProviders)
{
if (provider.IsServiceSupported(candidateContent.ServiceType))
{
Task<bool> task = provider.UpdateThumbnail(candidateContent);
tasks.Add(task);
break;
}
}
}
var results = await Task.WhenAll(tasks);
tasks.Clear();
foreach (var candidateContent in candidateContents)
{
if (candidateContent.ThumbnailLink != null && !candidateContent.ThumbnailLink.Equals(candidateContent.FileIconLink, StringComparison.CurrentCultureIgnoreCase))
{
Task<bool> task = DownloadAndUpdateThumbnailCache(candidateContent);
tasks.Add(task);
}
}
await Task.WhenAll(tasks);
//Clean up for next time the loop comes in.
tasks.Clear();
candidateContents.Clear();
lock (syncObject)
{
count = internalQueue.Count;
if (count == 0)
{
isQueueControllerRunning = false;
monitorEvent.Reset();
}
}
await Task.Run(() => monitorEvent.WaitOne());
}
}
private Content GetNextFromInternalQueue()
{
lock (syncObject)
{
Content nextContent = null;
if (internalQueue.Count > 0)
{
nextContent = internalQueue[0];
internalQueue.Remove(nextContent);
}
return nextContent;
}
}
public void AddToUpdateQueue(Content content)
{
lock (syncObject)
{
internalQueue.Add(content);
if (!isQueueControllerRunning)
{
isQueueControllerRunning = true;
monitorEvent.Set();
}
}
}
You should simply use TPL Dataflow. It's an actor framework on top of the TPL with an async support. Use an ActionBlock with an async action and MaxDegreeOfParallelism of 5:
var block = new ActionBlock<Content>(
async content =>
{
var tasks = interactionProviders.
Where(provider => provider.IsServiceSupported(content.ServiceType)).
Select(provider => provider.UpdateThumbnail(content));
await Task.WhenAll(tasks);
if (content.ThumbnailLink != null && !content.ThumbnailLink.Equals(
content.FileIconLink,
StringComparison.CurrentCultureIgnoreCase))
{
await DownloadAndUpdateThumbnailCache(content);
}
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5});
foreach (var content in GetContent())
{
block.Post(content);
}
block.Complete();
await block.Completion
I have a little problem with Threads in this code..
I just want to run a lot of tasks together, and continue when all of them finish.
while (true)
{
// Run tasks together:
foreach (object T in objectsList)
{
if (T.something>0)
var task = Task.Factory.StartNew(() => T.RunObject());
task.ContinueWith(delegate { ChangeObject(T, 1); }, TaskContinuationOptions.NotOnFaulted);
}
// <-- Here I want to wait for all the task to be finish.
// I know its task.Wait() but how to waitAll()?
System.Threading.Thread.Sleep(this.GetNextTime());
var RefreshObjects = new Task(loadObjectsList); RefreshObjects .Start(); RefreshObjects.Wait();
}
I don't know how many objects will be in objectsList and I don't know if T.something will be > 0.
so I can't just use:
Task[] Tasks = new Task[objectsList.count()]
for (int T=0; T<objectsList.count(); ++T)
{
if (objectsList[T].something>0)
var task = Task.Factory.StartNew(() => objectsList[T].RunObject());
task.ContinueWith(delegate { ChangeObject(objectsList[T], 1); }, ...);
}
Task.WaitAll(Tasks);
Because Tasks will contains nulls when objectsList[T].something!>0...
Thanks for any advice!
Just switch the condition and create a List of tasks only for the objects which matches your criteria.
var tasks = objectsList
.Where(x => x.Something() > 0)
.Select(x => {
var task = Task.Factory.StartNew(() => x.RunObject());
task.ContinueWith(t => ChangeObject(....));
return task;
})
.ToArray();
Task.WaitAll(tasks);
Your code sample just waits for RunObject()to complete! If this is desired skip the rest of my answer. If you want to wait for the continuation to complete, too you can use this
var tasks = objectsList
.Where(x => x.Something() > 0)
.Select(x => Task.Factory.StartNew(() => x.RunObject()).ContinueWith(t => ChangeObject(....)))
.ToArray();
Task.WaitAll(tasks);
because ContinueWith generates a new Task.
If objectsList implements IEnumerable, (as an array does),
(And there are less than 64 objects in the list), you can use this:
public delegate void SyncDelegatesInParallelDelegate<in T>(T item);
public static class ParallelLinqExtensions
{
public static void SyncDelegatesInParallel<T>(
this IEnumerable<T> list,
SyncDelegatesInParallelDelegate<T> action)
{
var foundCriticalException = false;
Exception exception = null;
var waitHndls = new List<WaitHandle>();
foreach (var item in list)
{
// Temp copy of session for modified closure
var localItem = item;
var txEvnt = new ManualResetEvent(false);
// Temp copy of session for closure
ThreadPool.QueueUserWorkItem(
depTx =>
{
try { if (!foundCriticalException) action(localItem); }
catch (Exception gX)
{ exception = gX; foundCriticalException = true; }
finally { txEvnt.Set(); }
}, null);
waitHndls.Add(txEvnt);
}
if (waitHndls.Count > 0) WaitHandle.WaitAll(waitHndls.ToArray());
if (exception != null) throw exception;
}
}
you would call it like this
objectsList.SyncDelegatesInParallel(delegate { ChangeObject(T, 1);});
I have async methods that returns an objects
public static IEnumerable<Users.User> GetUsers(IEnumerable<string> uids, Field fields)
{
Task<ResponseApi<Users.User>>[] tasks;
tasks = uids.Select(uid =>
{
var parameters = new NameValueCollection
{
{"uids", uid},
{"fields", FieldsUtils.ConvertFieldsToString(fields)}
};
return GetUserResponseApi(parameters);
}).ToArray();
Task.WaitAll(tasks);
foreach(Task<ResponseApi<Users.User>> task in tasks)
{
if(task.Result.Response != null)
{
yield return task.Result.Response;
}
}
}
And I want to update UI in other method,UI maybe don't update because method GetUserResponseApi don't return any value.
public static Task<ResponseApi<Users.User>> GetUserResponseApi(NameValueCollection parameters)
{
return CallMethodApi("users.get", parameters, CallType.HTTPS)
.ContinueWith(
r =>
{
//don't execute
var responseApi = new ResponseApi<Users.User>();
responseApi.Response = JsonConvert.DeserializeObject<Users.User>(r.Result["response"][0].ToString());
return responseApi;
});
}
private void BtnGetUsersClick(object sender, EventArgs e)
{
var random = new Random();
int max = 175028595;
var uids = new List<string>();
for(int i = 1; i <= 20; i++)
{
uids.Add((random.Next(max) + 1).ToString());
}
Task.Factory.StartNew(() =>
{
var users = VkontakteApi.GetUsers(uids, Field.Online);
foreach(var user in users)
{
richTextBox1.Text += string.Format("ID:{0} online:{1}", user.uid,
user.online);
}
}, CancellationToken.None, TaskCreationOptions.None, _uiContext);
}
How to resolve problem with ContinueWith in GetUserResponseApi?
UPDATE:
I think that problem in method GetUserResponseApi because block ContinueWith doesn't execute.
Use Application.Current.Dispatcher to dispatch calls to UI thread whenever you access UI objects.
Application.Current.Dispatcher.Invoke(() => {
try
{
richTextBox1.Text += string.Format("ID:{0} online:{1}", user.uid, user.online);
}
catch
{
//handle
}
), DispatcherPriority.Background);
Try using TaskScheduler.FromCurrentSynchronizationContext() method:
Task.Factory.StartNew(() =>
{
var users = VkontakteApi.GetUsers(uids, Field.Online);
foreach(var user in users)
{
richTextBox1.Text += string.Format("ID:{0} online:{1}", user.uid,
user.online);
}
}, CancellationToken.None,
TaskScheduler.FromCurrentSynchronizationContext());