I have a headless UWP application that uses an external library to connect to a serial device and send some commands. It runs an infinite loop (while true) with a 10 minute pause between loops. The measurement process takes around 4 minutes.
The external library needs to run 3 measurements and after each it signals by raising an event. When the event is raised the 4th time I know that I can return the results.
After 4 hours (+/- a few seconds) the library stops raising events (usually it raises the event one or 2 times and then it halts, no errors, nothing).
I implemented in DoMeasureAsync() below a CancellationTokenSource that was supposed to set the IsCancelled property on the TaskCompletionSource after 8 minutes so that the task returns and the loop continues.
Problem:
When the measurement does not complete (the NMeasureCompletionSource never gets its result set in class CMeasure), the task from nMeasureCompletionSource is never cancelled. The delegate defined in RespondToCancellationAsync() should run after the 8 minutes.
If the measurement runs ok, I can see in the logs that the code in the
taskAtHand.ContinueWith((x) =>
{
Logger.LogDebug("Disposing CancellationTokenSource...");
cancellationTokenSource.Dispose();
});
gets called.
Edit:
Is it possible that the GC comes in after the 4 hours and maybe deallocates some variables and doing so makes the app to not be able to send the commands to the sensor? - It is not the case
What am I missing here?
//this gets called in a while (true) loop
public Task<PMeasurement> DoMeasureAsync()
{
nMeasureCompletionSource = new TaskCompletionSource<PMeasurement>();
cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(8));
var t = cMeasure.Run(nitrateMeasureCompletionSource, cancellationTokenSource.Token);
var taskAtHand = nitrateMeasureCompletionSource.Task;
taskAtHand.ContinueWith((x) =>
{
Logger.LogDebug("Disposing CancellationTokenSource...");
cancellationTokenSource.Dispose();
});
return taskAtHand;
}
public class CMeasure
{
public async Task Run(TaskCompletionSource<PMeasurement> tcs, CancellationToken cancellationToken)
{
try
{
NMeasureCompletionSource = tcs;
CancellationToken = cancellationToken;
CancellationToken.Register(async () => await RespondToCancellationAsync(), useSynchronizationContext: false);
CloseDevice(); //Closing device if for some reason is still open
await Task.Delay(2500);
TheDevice = await GetDevice();
measurementsdone = 0;
Process(); //start the first measurement
}
catch (Exception ex)
{
DisconnectCommManagerAndCloseDevice();
NMeasureCompletionSource.SetException(ex);
}
}
public async Task RespondToCancellationAsync()
{
if (!NitrateMeasureCompletionSource.Task.IsCompleted)
{
Logger.LogDebug("Measure Completion Source is not completed. Cancelling...");
NMeasureCompletionSource.SetCanceled();
}
DisconnectCommManagerAndCloseDevice();
await Task.Delay(2500);
}
private void Process()
{
if (measurementsdone < 3)
{
var message = Comm.Measure(m); //start a new measurement on the device
}
else
{
...
NMeasureCompletionSource.SetResult(result);
}
}
//the method called when the event is raised by the external library
private void Comm_EndMeasurement(object sender, EventArgs e)
{
measurementsdone++;
Process();
}
}
After more testing I have reached the conclusion that there is no memory leak and that all the objects are disposed. The cancellation works well also.
So far it appears that my problem comes from the execution of the headless app on the Raspberry Pi. Although I am using the deferral = taskInstance.GetDeferral(); it seems that the execution is stopped at some point...
I will test more and come back with the results (possibly in a new post, but I will put a link here as well).
Edit:
Here is the new post: UWP - Headless app stops after 3 or 4 hours
Edit 2:
The problem was from a 3rd party library that I had to use and it had to be called differently from a headless app. Internally it was creating its own TaskScheduler if SynchronizationContext.Current was null.
Related
I have an app (App1) that makes use of the WKWebView for a good portion of the UI. There is a scenario where an HTTP PUT request is sent from the WKWebView to a backend server to save some data. For this save operation to complete, the server will need approval thru another app (App2). The user would normally switch to App2 to approve, then switch back to App1 to see the result of the save. The problem is that when App1 gets backgrounded, it can cause the response to the save request to be cancelled, even though the save was completely successful on the backend server. There isn't any errors actually logged, but I'm fairly certain it is happening because iOS is killing the connection when the app gets suspended after it gets backgrounded. I'm basing my thoughts on this discussion.
Since the time it takes to approve the save on App2 isn't that long, I figured I could just try to extend the background time of App1, and it appears to work in the times I've tested it.
However, I want to know if this is really the best strategy, and if so, are there any recommendations on my code (For example, should I move the BeginBackgroundTask inside of the Task.Run):
I used these microsoft docs as an example.
public override async void DidEnterBackground(UIApplication application)
{
ExtendBackgroundTime(application);
}
private nint? webViewBgTaskId = null;
private CancellationTokenSource webViewBgTaskTokenSrc = null;
private void ExtendBackgroundTime(UIApplication application)
{
// cancel the previous background task that was created in this function
webViewBgTaskTokenSrc?.Cancel();
webViewBgTaskTokenSrc = null;
if (webViewBgTaskId.HasValue)
{
application.EndBackgroundTask(webViewBgTaskId.Value);
webViewBgTaskId = null;
}
var cts = new CancellationTokenSource();
nint taskId = default;
taskId = application.BeginBackgroundTask(() =>
{
cts.Cancel();
webViewBgTaskTokenSrc = null;
application.EndBackgroundTask(taskId);
webViewBgTaskId = null;
});
_ = Task.Run(async () =>
{
// For now, this is just set to 5 minutes, but in my experience,
// the background task will never be allowed to continue for that long.
// It's usually only about 30 seconds as of iOS 13.
// But this at least gives it some finite upper bound.
await Task.Delay(TimeSpan.FromMinutes(5), cts.Token);
application.EndBackgroundTask(taskId);
webViewBgTaskId = null;
}, cts.Token);
webViewBgTaskTokenSrc = cts;
webViewBgTaskId = taskId;
}
The following code snippet demonstrates registering a task to run in the background:
nint taskID = UIApplication.SharedApplication.BeginBackgroundTask( () => {});
//runs on main or background thread
FinishLongRunningTask(taskID);
UIApplication.SharedApplication.EndBackgroundTask(taskID);
The registration process pairs a task with a unique identifier, taskID, and then wraps it in matching BeginBackgroundTask and EndBackgroundTask calls. To generate the identifier, we make a call to the BeginBackgroundTask method on the UIApplication object, and then start the long-running task, usually on a new thread. When the task is complete, we call EndBackgroundTask and pass in the same identifier. This is important because iOS will terminate the application if a BeginBackgroundTask call does not have a matching EndBackgroundTask.
Note: If you want to perform Tasks During DidEnterBackground method, these tasks must be invoked on a separate thread. Therefore, sample project uses Task to invoke FinishLongRunningTask.
Task.Factory.StartNew(() => FinishLongRunningTask(taskID));
I'm attempting to use the CancellationToken with SqlConnection.OpenAsync() to limit the amount of time that the OpenAsync function takes.
I create a new CancellationToken and set it to cancel after say 200 milliseconds. I then pass it to OpenAsync(token). However this function still can take a few seconds to run.
Looking at the documentation I cant really see what I'm doing wrong.
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.openasync?view=netframework-4.7.2
Here is the code I'm using:
private async void btnTestConnection_Click(object sender, EventArgs e)
{
SqlConnection connection = new SqlConnection(SQLConnectionString);
Task.Run(() => QuickConnectionTest(connection)).Wait();
}
public async Task QuickConnectionTest(SqlConnection connection)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
source.CancelAfter(200);
ConnectionOK = false;
try
{
using (connection)
{
await connection.OpenAsync(token);
if (connection.State == System.Data.ConnectionState.Open)
{
ConnectionOK = true;
}
}
}
catch (Exception ex)
{
ErrorMessage = ex.ToString();
}
}
I was expecting OpenAsync() to end early when the CancellationToken to throw a OperationCanceledException when 200ms had passed but it just waits.
To replicate this I do the following:
Run the code: Result = Connection OK
Stop the SQL Service
Run the code: hangs for the length of connection.Timeout
Your code seems correct to me. If this does not perform cancellation as expected then SqlConnection.OpenAsync does not support reliable cancellation. Cancellation is cooperative. If it's not properly supported, or if there is a bug, then you have no guarantees.
Try upgrading to the latest .NET Framework version. You can also try .NET Core which seems to lead the .NET Framework. I follow the GitHub repositories and have seen multiple ADO.NET bugs related to async.
You can try calling SqlConnection.Dispose() after the timeout passes. Maybe that works. You also can try using the synchronous API (Open). Maybe that uses a different code path. I believe it does not, but it is worth a try.
In case that does not work either you need to write your code so that your logic proceeds even if the connection task has not completed. Pretend that it has completed and let it linger in the background until it completes by itself. This can cause increased resource usage but it might be fine.
In any case consider opening an issue in the GitHub corefx repository with a minimal repro (like 5 lines connecting to example.com). This should just work.
The CancelationToken works the same for everyone else that it does for you in your own code.
Example:
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("main");
var cts = new CancellationTokenSource();
var task = SomethingAsync(cts.Token);
cts.Cancel();
await task;
Console.WriteLine("Complete");
Console.ReadKey();
}
static async Task SomethingAsync(CancellationToken token)
{
Console.WriteLine("Started");
while (!token.IsCancellationRequested)
{
await Task.Delay(2000); //didn't pass token here because we want to simulate some work.
}
Console.WriteLine("Canceled");
}
}
//**Outputs:**
//main
//Started
//… then ~2 seconds later <- this isn't output
//Canceled
//Complete
The OpenAsync method may require some optimization but don't expect a call to Cancel() to immediately cancel any Task. It's just a marshalled flag to let the unit of work within the Task know that the caller wants to cancel it. The work inside the Task gets to choose how and when to cancel. If it's busy when you set that flag then you just have to wait and trust the Task is doing what it needs to do to wrap things up.
In Brief
I have a Windows Service that executes several jobs as async Tasks in parallel. However, when the OnStop is called, it seems that these are all immediately terminated instead of being allowed to stop in a more gracious manner.
In more detail
Each job represents an iteration of work, so having completed its work the job then needs to run again.
To accomplish this, I am writing a proof-of-concept Windows Service that:
runs each job as an awaited async TPL Task (these are all I/O bound tasks)
each job is run iteratively within a loop
each job's loop is run in parallel
When I run the Service, I see everything executing as I expect. However, when I Stop the service, it seems that everything stops dead.
Okay - so how is this working?
In the Service I have a cancellation token, and a TaskCompletion Source:
private static CancellationTokenSource _cancelSource = new CancellationTokenSource();
private TaskCompletionSource<bool> _jobCompletion = new TaskCompletionSource<bool>();
private Task<bool> AllJobsCompleted { get { return _finalItems.Task; } }
The idea is that when every Job has gracefully stopped, then the Task AllJobsCompleted will be marked as completed.
The OnStart simply starts running these jobs:
protected override async void OnStart(string[] args)
{
_cancelSource = new CancellationTokenSource();
var jobsToRun = GetJobsToRun(); // details of jobs not relevant
Task.Run(() => this.RunJobs(jobsToRun, _cancelSource.Token).ConfigureAwait(false), _cancelSource.Token);
}
The Task RunJobs will run each job in a parallel loop:
private async Task RunModules(IEnumerable<Jobs> jobs, CancellationToken cancellationToken)
{
var parallelOptions = new ParallelOptions { CancellationToken = cancellationToken };
int jobsRunningCount = jobs.Count();
object lockObject = new object();
Parallel.ForEach(jobs, parallelOptions, async (job, loopState) =>
{
try
{
do
{
await job.DoWork().ConfigureAwait(false); // could take 5 seconds
parallelOptions.CancellationToken.ThrowIfCancellationRequested();
}while(true);
}
catch(OperationCanceledException)
{
lock (lockObject) { jobsRunningCount --; }
}
});
do
{
await Task.Delay(TimeSpan.FromSeconds(5));
} while (modulesRunningCount > 0);
_jobCompletion.SetResult(true);
}
So, what should be happening is that when each job finishes its current iteration, it should see that the cancellation has been signalled and it should then exit the loop and decrement the counter.
Then, when jobsRunningCount reaches zero, then we update the TaskCompletionSource. (There may be a more elegant way of achieving this...)
So, for the OnStop:
protected override async void OnStop()
{
this.RequestAdditionalTime(100000); // some large number
_cancelSource.Cancel();
TraceMessage("Task cancellation requested."); // Last thing traced
try
{
bool allStopped = await this.AllJobsCompleted;
TraceMessage(string.Format("allStopped = '{0}'.", allStopped));
}
catch (Exception e)
{
TraceMessage(e.Message);
}
}
What what I expect is this:
Click [STOP] on the Service
The Service should take sometime to stop
I should see a trace statement "Task cancellation requested."
I should see a trace statement saying either "allStopped = true", or the exception message
And when I debug this using a WPF Form app, I get this.
However, when I install it as a service:
Click [STOP] on the Service
The Service stops almost immediately
I only see the trace statement "Task cancellation requested."
What do I need to do to ensure the OnStop doesn't kill off my parallel async jobs and waits for the TaskCompletionSource?
Your problem is that OnStop is async void. So, when it does await this.AllJobsCompleted, what actually happens is that it returns from OnStop, which the SCM interprets as having stopped, and terminates the process.
This is one of the rare scenarios where you'd need to block on a task, because you cannot allow OnStop to return until after the task completes.
This should do it:
protected override void OnStop()
{
this.RequestAdditionalTime(100000); // some large number
_cancelSource.Cancel();
TraceMessage("Task cancellation requested."); // Last thing traced
try
{
bool allStopped = this.AllJobsCompleted.GetAwaiter().GetResult();
TraceMessage(string.Format("allStopped = '{0}'.", allStopped));
}
catch (Exception e)
{
TraceMessage(e.Message);
}
}
I am attempting to create a windows service that polls every 5 minutes a system and checks for some action that needs done. I have read up on WaitHandles and their usefulness in this area, but need to understand how this works.
See code below:
public partial class PollingService : ServiceBase
{
private CancellationTokenSource cancelToken = new CancellationTokenSource();
private Task mainTask = null;
public PollingService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
mainTask = new Task(pollInterval, cancelToken.Token, TaskCreationOptions.LongRunning);
mainTask.Start();
}
public void pollInterval()
{
CancellationToken cancel = cancelToken.Token;
TimeSpan interval = TimeSpan.FromMinutes(5);
while (!cancel.IsCancellationRequested && !cancel.WaitHandle.WaitOne(interval))
{
if (cancel.IsCancellationRequested)
{
break;
}
EventLog.WriteEntry("*-HEY MAN I'M POLLNG HERE!!-*");
//Polling code goes here. Checks periodically IsCancellationRequested
}
}
protected override void OnStop()
{
cancelToken.Cancel();
mainTask.Wait();
}
}
The above code seems like something that should work from my research, but I don't understand the !cancel.WaitHandle.WaitOne(interval) portion. How does this keep the loop going with a wait every five minutes? I need to understand this part of the code to complete my script, or to know if I am completely wrong in my use of the WaitHandle.
This was where I got the idea: Creating a c# windows service to poll a database
As the article Hans pointed you to explains, the usage here is to have a way to have the thread wait for some specific period of time, but still allow the thread to be woken up prior to the timeout period expiring, e.g. in case you need the thread to terminate early (as here).
That said, this implementation is "old school". :) If you are using .NET 4.5, IMHO the code would work better if you use the async/await idiom (especially since you're already using CancellationTokenSource):
protected async override void OnStart(string[] args)
{
try
{
await pollInterval();
}
catch (TaskCanceledException) { }
}
public async Task pollInterval()
{
CancellationToken cancel = cancelToken.Token;
TimeSpan interval = TimeSpan.FromMinutes(5);
while (true)
{
await Task.Delay(interval, cancel);
EventLog.WriteEntry("*-HEY MAN I\"M POLLNG HERE!!-*");
//Polling code goes here. Checks periodically IsCancellationRequested
}
}
With the above, the code more correctly expresses the intent. That is, whereas the WaitHandle version appears primarily to be waiting to be signaled to exit, even though the primary mechanism at work is actually the timeout for the wait, here the code clearly indicates that the primary intent is to delay, with the possibility of the delay being cancelled.
I have a really strange problem and I don't know how to solve it.
I have these two methods in different classes.
The first one is triggered when a button in the CommandBar is pressed.
EDIT: I created two similar but smaller methods to show you the problem:
private async void runCode(object sender, RoutedEventArgs e)
{
BottomAppBar.IsEnabled = false;
object result = await endlessLoopTest();
BottomAppBar.IsEnabled = true;
}
private async Task<object> endlessLoopTest()
{
var tokenSource = new System.Threading.CancellationTokenSource(500);
try
{
await Task.Run(() =>
{
while (true)
{
//Infinite loop to test the code
}
}, tokenSource.Token);
return null;
}
catch (OperationCanceledException)
{
return new TextBlock();
}
}
I added a cancellationToken that expires after 1500ms (I assume that if the interpreter takes longer to process the code, it has been trapped in a loop).
The first time I try this it usually works, but if I try again, the CommandBar buttons never get enabled again, so I assume that task is being awaited forever, and I don't know why, as I added that cancellationToken.
Do you know what could be wrong here?
Thanks for your help!
Sergio
You are about 2/3's of the way there. When using a CancellationToken + CancellationTokenSournce, one must ask the token if it was cancelled. There are a number of ways to subscribe to that, including calling the token's ThrowIfCancelledRequest method or checking the token's Boolean property IsCancellationRequested and breaking out of the loop. See Cancellation in Managed Threads.
Here's a small example that can run in a Console app. Note, in UI based apps, use await, not Task.Wait().
private static void CancelTask()
{
CancellationTokenSource cts = new CancellationTokenSource(750);
Task.Run(() =>
{
int count = 0;
while (true)
{
Thread.Sleep(250);
Console.WriteLine(count++);
if (cts.Token.IsCancellationRequested)
{
break;
}
}
}, cts.Token).Wait();
}
The result is 0 1 2 and then the Task and program exit.