Cancelling task from taskschedule cancels all other tasks in it - task-parallel-library

First of all, i'm really new to multithreading and to tpl in particular, so the code below could be bad. Really bad:)
So, I have following objective:
There is task queue, tasks proceeds inline, and progress of task "working" should be on the ProgressBar.
I was overloaded TaskScheduler class:
public class MyTaskScheduler:TaskScheduler
{
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
private static Logger _logger;
private readonly TaskQueue _scheduledTasks;
private readonly int _maxDegreeOfParallelism;
private int _delegatesQueuedOrRunning = 0;
public MyTaskScheduler(int maxDegreeOfParallelism)
{
_logger = LogManager.GetLogger("log");
_maxDegreeOfParallelism = maxDegreeOfParallelism;
_scheduledTasks = new TaskQueue();
}
public override int MaximumConcurrencyLevel { get { return 1; } }
protected override IEnumerable<Task> GetScheduledTasks()
{
lock (_scheduledTasks)
{
return _scheduledTasks.ToList();
}
}
protected override void QueueTask(Task task)
{
lock (_scheduledTasks)
{
_scheduledTasks.Enqueue(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
_logger.Info("Task queued");
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (!_currentThreadIsProcessingItems) return false;
if (taskWasPreviouslyQueued)
if (TryDequeue(task))
return TryExecuteTask(task);
else
return false;
return TryExecuteTask(task);
}
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
_currentThreadIsProcessingItems = true;
try
{
while (true)
{
Task item;
lock (_scheduledTasks)
{
if (_scheduledTasks.Count == 0)
{
--_delegatesQueuedOrRunning;
break;
}
item = _scheduledTasks.Dequeue();
}
_logger.Info("Execution of thread with id {0} started",item.Id);
TryExecuteTask(item);
_logger.Info("Execution of thread with id {0} ended", item.Id);
MessageBox.Show("ggg");
}
}
finally
{
_currentThreadIsProcessingItems = false;
}
}
, null);
}
}
And there is the part of code I initialize my queue:
private readonly List<Task> _tasks = new List<Task>();
readonly CancellationTokenSource _cts = new CancellationTokenSource();
private MyTaskScheduler _scheduler;
readonly TaskFactory _factory;
private int _currentNumberOfThreads;
public Form1(bool isMainForm)
{
InitializeComponent();
_logger = LogManager.GetLogger("log");
_isMainForm = isMainForm;
_currentNumberOfThreads = 0;
_scheduler = new MyTaskScheduler(1);
_factory = new TaskFactory(_scheduler);
if (_isMainForm == false)
{
_currentNumberOfThreads++;
var t = _factory.StartNew(() => DoWork(_cts.Token), _cts.Token);
_tasks.Add(t);
}
It works perfectly until i try to cancel current task.
private void button2_Click(object sender, EventArgs e)
{
_logger.Info("Initialized cancelling of current thread");
_cts.Cancel();
_currentNumberOfThreads--;
}
This canceling not only current task, but all tasks in queue as i can see from logs:
01:56:18 Task queued
01:56:18 Execution of thread with id 1 started
01:56:18 Thread's DoWork() begun
01:56:19 Task queued
01:56:22 Task queued
01:56:30 Thread {0} DoWork() ended
01:56:30 Execution of thread with id 1 ended
01:56:31 Execution of thread with id 2 started
01:56:31 Thread's DoWork() begun
01:56:32 Task queued
01:56:32 Task queued
01:56:33 Task queued
01:56:34 Initialized cancelling of current thread
01:56:34 Execution of thread with id 2 ended
01:56:35 Execution of thread with id 3 started
01:56:35 Execution of thread with id 3 ended
01:56:36 Execution of thread with id 4 started
01:56:36 Execution of thread with id 4 ended
01:56:36 Execution of thread with id 5 started
01:56:36 Execution of thread with id 5 ended
01:56:37 Execution of thread with id 6 started
01:56:37 Execution of thread with id 6 ended
Task cancellation should cancel only current task and next task in queue should start and work as usual.
I'm really confused and don't know what am I doing wrong. I have an idea that CancellationTokenSource I used to cancel my task, refers to the whole schedule, not to concrete task, but I can't prove it myself. So any help will be appreciated.
P.S. Sorry for my bad English.

It's actually pretty simple:
you are using one single cancellation token as a field.
readonly CancellationTokenSource _cts = new CancellationTokenSource();
If you want to cancel only one single task, you need to create one token per task, and only cancel the one passed to the task you want to abort. simple as that :)

Related

Dotnet yield with IEnumerable<Task<int>> not working

Playing around with yield and Task.
The following simple example runs fine.
class Program
{
private static void Main(string[] args)
{
string[] messages = { "First task", "Second task", "Third task", "Fourth task" };
var taskList = CreateTaskList(messages).ToList();
taskList.ForEach(task => task.Start());
Task.WaitAll(taskList.ToArray());
Console.WriteLine("Main method complete. Press enter to finish.");
Console.ReadLine();
}
static IEnumerable<Task> CreateTaskList(string[] messages)
{
foreach (var message in messages)
{
yield return new Task(obj => PrintMessage((string)obj!), message);
}
}
static void PrintMessage(object message)
{
Console.WriteLine("Message: {0}", message);
}
}
But the following does not. Is some deadlock at play? Its stuck at Task.WaitAll. So all that I get from Console is Before wait all
class SimpleClass
{
public int Counter { get; set; }
}
class Program
{
private static void Main(string[] args)
{
// create the simple object
var simpleObject = new SimpleClass();
var taskList = CreateTaskEnumerable(simpleObject, 10);
// Start all of the tasks
foreach (var task in taskList)
task.Start();
Console.WriteLine("Before wait all");
// wait for all of the tasks to complete
Task.WaitAll(taskList.ToArray());
Console.WriteLine("After wait all");
foreach (var task in taskList)
simpleObject.Counter += task.Result;
// write out the counter value
Console.WriteLine("Expected value {0}, Counter value: {1}", 10000, simpleObject.Counter);
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
private static IEnumerable<Task<int>> CreateTaskEnumerable(SimpleClass simpleObject, int numberOfTasks)
{
for (int i = 0; i < numberOfTasks; i++)
{
yield return new Task<int>((stateObject) =>
{
// get the state object
var localCounter = (int)stateObject!;
// enter a loop for 1000 increments
for (int j = 0; j < 1000; j++)
// increment the counters
localCounter++;
return localCounter;
}, simpleObject.Counter);
}
}
}
If I remove yield altogether, the above will be as follows, and works. It gives the output as follows. I am expecting the same output from the above as well, but thats stuck. Why?
Before wait all
After wait all
Expected value 10000, Counter value: 10000
Press enter to finish
The program.
class SimpleClass
{
public int Counter { get; set; }
}
class Program
{
private static void Main(string[] args)
{
// create the bank account instance
var simpleObject = new SimpleClass();
// create an list of tasks
var taskList = new List<Task<int>>();
for (int i = 0; i < 10; i++)
{
// create a new task
var task = new Task<int>((stateObject) =>
{
// get the state object
var localCounter = (int)stateObject!;
// enter a loop for 1000 increments
for (int j = 0; j < 1000; j++)
// increment the counters
localCounter++;
return localCounter;
}, simpleObject.Counter);
taskList.Add(task);
}
// Start all of the tasks
foreach (var task in taskList)
task.Start();
Console.WriteLine("Before wait all");
// wait for all of the tasks to complete
Task.WaitAll(taskList.ToArray());
Console.WriteLine("After wait all");
foreach (var task in taskList)
simpleObject.Counter += task.Result;
// write out the counter value
Console.WriteLine("Expected value {0}, Counter value: {1}", 10000, simpleObject.Counter);
// wait for input before exiting
Console.WriteLine("Press enter to finish");
Console.ReadLine();
}
}
In the first example you are calling CreateTaskList(messages).ToList() which forces CreateTaskList to yield all tasks before continuing. In the second example you do not call ToList(), and the tasks are yielded in the foreach and then started. The problem is in line Task.WaitAll(taskList.ToArray());. It takes the IEnumerable and yields the tasks again, and you are waiting for them to finish, but they are not started. In other words, every time you call foreach or ToList() on your 'yielded' IEnumerable, it will run the method CreateTaskEnumerable and create new tasks.
One solution is to call var taskList = CreateTaskEnumerable(simpleObject, 10).ToList() or you could just manualy create list in CreateTaskEnumerable and return it.
P.S. I would suggest you read how yield return works, or test it in https://sharplab.io/. It basically creates IEnumerable that gets its data from your method. This means your method will be executed every time your IEnumerable is enumerated.

UWP Timer Trigger Background Task Get's Suspended

I am working on BackgroundTask on UWP and have been trying to run this task but for some unknown reason it always ends up showing suspended in Task Manager and doesn't run. Below is the code
This how I am registering my BackgroundTask
public static async Task InitializeLatestNewsTaskAsync()
{
TimeTrigger hourlyTrigger = new TimeTrigger(15, false);
BackgroundExecutionManager.RemoveAccess();
var requestStatus = await BackgroundExecutionManager
.RequestAccessKindAsync(BackgroundAccessRequestKind.AlwaysAllowed, "Please allow the app to run in background");
if (!requestStatus)
{
Util.MessageBox("Warrning", "You will not receive any Announcements notifications");
}
else
{
try
{
BackgroundTaskRegistration backgroundTaskRegistration = RegisterBackgroundTask(Constants.BACKGROUND_TASK_LATEST_NEWS_ENTRY_POINT,
Constants.BACKGROUND_TASK_LATEST_NEWS_NAME, hourlyTrigger, null);
}
catch (Exception e)
{
log.Error(e.Message, e);
}
}
}
//
// Register a background task with the specified taskEntryPoint, name, trigger,
// and condition (optional).
//
// taskEntryPoint: Task entry point for the background task.
// taskName: A name for the background task.
// trigger: The trigger for the background task.
// condition: Optional parameter. A conditional event that must be true for the task to fire.
//
private static BackgroundTaskRegistration RegisterBackgroundTask(
string taskEntryPoint, string taskName, IBackgroundTrigger trigger, IBackgroundCondition condition)
{
//
// Check for existing registrations of this background task.
//
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == taskName)
{
//
// The task is already registered.
//
BackgroundTaskRegistration backgroundTaskRegistration = (BackgroundTaskRegistration)(cur.Value);
backgroundTaskRegistration.Unregister(true);
break;
}
}
//
// Register the background task.
//
var builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = taskEntryPoint;
builder.SetTrigger(trigger);
builder.IsNetworkRequested = true;
if (condition != null)
{
builder.AddCondition(condition);
}
BackgroundTaskRegistration task = builder.Register();
task.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
return task;
}
private static async void OnCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
{
log.Info("Completed");
}
And Here is my Package.appxmanifest Declarations
and My BackgroundTask Implementation
public sealed class LatestNewsBackgroundTask : IBackgroundTask
{
private ILogger log = LogManagerFactory.DefaultLogManager.GetLogger<LatestNewsBackgroundTask>();
BackgroundTaskDeferral _deferral;
public void Run(IBackgroundTaskInstance taskInstance)
{
log.Info("Latest New Background Task Started");
_deferral = taskInstance.GetDeferral();
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
var announcementTypes = localSettings.Values["announcementTypes"];
NotificationHelper.GenerateToast("working from background task ", announcementTypes.ToString());
log.Info("Latest New Background Task Completed");
_deferral.Complete();
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
// Indicate that the background task is canceled.
log.Info("Background " + sender.Task.Name + " Cancel Requested...");
}
}
What all I have tried to debug
https://learn.microsoft.com/en-us/windows/uwp/launch-resume/debug-a-background-task

How to cancel BackgroundJob or let a newjob don't retry?

my case similar the example, a user presses a 'report' button to start a long-running reporting job. You add this job to the queue and send the report's result to your user via email when it's completed. But, I don't need retries job execution when the job failed. I want job stop and send notification. And can cancel on service.
public void CancelAllMyJob()
{
var jobs = backgroundJobStore.GetWaitingJobsAsync(int.MaxValue).Result.Where(o => o.JobType.Contains(nameof(MyJob))).ToList();
if (jobs.Count > 0)
{
foreach (BackgroundJobInfo job in jobs)
{
backgroundJobStore.DeleteAsync(job).Wait();
}
}
}
public async Task StartMyJob(MyJobInput input)
{
MyJobArgs args = new MyJobArgs
{
TenantId = AbpSession.TenantId,
UserId = AbpSession.UserId.Value,
};
var id = await backgroundJobManager.EnqueueAsync<MyJob, MyJobArgs >(args);
(await backgroundJobStore.GetAsync(Convert.ToInt64(id))).TryCount = 0;
}
public async Task StartMyJob(MyJobInput input)
{
MyJobArgs args = new MyJobArgs
{
TenantId = AbpSession.TenantId,
UserId = AbpSession.UserId.Value,
};
await Task.Run(() =>
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
{
myJob.Execute(args);
}
});
}
It's work, thanks!

Await Async : how to get one method completed before the second one starts

How to I make the call so that the GetRecords is completed before _reportViewerService.ShowReport starts. Using SignalR the setUi updates a txt field which displays names from part of the result calculated in getRecords, and the rest should be printed in report there after.
(whats happening now is both running the same time, then the report being showed before I see the live update status)
Thanks in advance
public async Task ViewReport()
{
var reportData = await _apiCallExecutor.ExecuteAsync(new GetRecords(queryModel, setUiHooks));
try
{
if (reportData.Count > 0)
{
var settings = new ReportSettings();
settings.ReportPath = "Utilities/SetDeliveryIdByBatchReport";
settings.ReportTitle = "Set Delivery ID By Batch - Exception Listing";
settings.DataSources.Add("DeliveryIdExceptionRecords", reportData);
ReportStatus = "Printing Exception Report...";
await _reportViewerService.ShowReport(settings);
}
}
finally
{
ViewModelState = ViewModelStates.Edit;
}
ReportStatus = "Done...";
}
You want to use some kind of "signal" from GetRecords, e.g., an IObservable or Task that completes when the data has arrived.
class GetRecords
{
...
public Task Done { get; }
// or: public IObservable<Unit> Done { get; }
}
then:
var getRecords = new GetRecords(queryModel, setUiHooks);
var reportData = await _apiCallExecutor.ExecuteAsync(getRecords);
await getRecords.Done;
...

Semaphores and Lock in ASP MVC3

I am working with Asp.net MVC 3. Any reason why I cannot create a single instance of a thread process in MVC? I am trying to allow a single instance of a worker process one at a time. however it is allowing more than one instance at a time.
I followed the example at:
http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx
This is the code in my controller:
private static Semaphore _pool;
public ActionResult StartBots(int id)
{
_pool = new Semaphore(0, 1);
Thread t = new Thread(SingletonWorker);
t.Start();
_pool.Release(1);
return RedirectToAction("index", new { id = id });
}
I also tried this example using lock:
http://msdn.microsoft.com/en-us/library/c5kehkcz(v=VS.80).aspx
private Object thisLock = new Object();
public ActionResult StartBots(int id)
{
Thread t = new Thread(SingletonWorker);
lock (thisLock)
{
t.Start();
}
return RedirectToAction("index", new { id = id });
}
-------------------------------- Worker ------------------------------------
private static void SingletonWorker()
{
_pool.WaitOne(); <== this only applies to the Semaphore example.
// Do something here.
Thread.Sleep(rand.Next(4) * 200 + 1000);
// Do something else here.
}
Your code has several problems, but the most important one is - you only lock the Thread.Start, but that doesnt promise you'll have only one concurrent thread, it only means that only the thread creation is locked.
If what you want to force the threads to work serially, you could use the following code:
private static Object _lockKey = new Object();
public ActionResult SomeAction(int someParam)
{
ThreadPool.QueueUserWorkItem(doWork, SOME_STATE);
return SOMETHING;
}
private void doWork(object state)
{
lock (_lockKey)
{
/* Because of the static lockKey, this code will not be invoked serially */
}
}

Resources