Blazor Server System.ObjectDisposedException issues when updating pages - c#

Added the process to update the data every second in Blazor Server.
This works fine, but when I press refresh page (F5) in my browser I get the following error:
System.ObjectDisposedException: 'Cannot process pending renders after the renderer has been disposed.
ObjectDisposed_ObjectName_Name'
The target code is here
#code {
private List<Models.Recipe> recipes { get; set; }
private List<Models.NowOrder> nowOrders { get; set; }
private List<Models.PlanOrder> planOrders { get; set; }
System.Threading.Timer _timer;
protected override void OnInitialized()
{
using (var dbContext = DbFactory.CreateDbContext())
{
this.recipes = dbContext.Recipes.ToList();
this.planOrders = dbContext.PlanOrders.ToList();
this.nowOrders = dbContext.NowOrders.ToList();
}
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
databaseValue = await TimerProcessGetValue();
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
}
public void Dispose()
{
_timer?.Dispose();
}
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue = (await dbContext.TestTable.SingleAsync(x => x.Id == 1)).TestValue;
}
return timerProcessValue;
}
}
When refreshing the page "await InvokeAsync(StateHasChanged);"
If you comment out the following part, you will not get an error even if you press the F5 key, so I think that my handling of asynchronous processing is wrong, but I am troubled because I can not find the same case even if I search for the error I am.
What do you think is the cause of this?
Thank you for your cooperation.
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
databaseValue = await TimerProcessGetValue();
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
Change to verification code
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
//databaseValue = await TimerProcessGetValue();
await Task.Delay(500); //Added verification code.
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
}
Append.(10/10/2021)
Version used: net core 5.0.7.
Browser used: Edge.
I'm not using any other browser.
My environment is limited and I can only use Edge ...
Below are the three codes we have verified.
①This is an example using Dispose (WaitHandle).
This did not improve the error.
public void Dispose()
{
using(System.Threading.WaitHandle waitHandle = new System.Threading.ManualResetEvent(false))
{
if (_timer.Dispose(waitHandle))
{
const int millisecondsTimeout = 500;
if (!waitHandle.WaitOne(millisecondsTimeout))
{
System.Diagnostics.Debug.WriteLine("Dispose Test");
}
}
}
}
②This is an example using System.Timers.Timer.
the reload using the F5 key was successful.
private int currentCount = 0;
private Timer timer2 = new(1000);
protected override void OnInitialized()
{
timer2.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer2.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
Time = DateTime.Now.ToString();
databaseValue = TimerProcessGetValue().Result;
StateHasChanged();
});
}
public void Dispose() => timer2.Dispose();
③Added error handling.
Even with this method, I succeeded in reloading with the F5 key without any error.
try
{
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
databaseValue = await TimerProcessGetValue();
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
}
catch
{
// empty on purpose
}

https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-5.0
When a timer is no longer needed, use the Dispose method to free the resources held by the timer. Note that callbacks can occur after the Dispose() method overload has been called, because the timer queues callbacks for execution by thread pool threads. You can use the Dispose(WaitHandle) method overload to wait until all callbacks have completed.
This is why await InvokeAsync(StateHasChanged); is being called after the component is disposed.

To expand on #Brianparker's answer:
replace #implements IDisposable with #implements IAsyncDisposable
and then replace the Dispose() method with
public ValueTask DisposeAsync()
{
return _timer?.DisposeAsync() ?? ValueTask.CompletedTask;
}
Update
but I get the same error
I can't reproduce your error but I suppose that F5 is rather brutal.
You could try putting a try/catch aroud the StateHasChanged call:
try
{
await InvokeAsync(StateHasChanged);
}
catch
{ // empty on purpose
}

Related

Blazor WASM - Timer stops after some time [duplicate]

I have written a program that updates pages regularly with the Blazor server app.
When this program is executed, the current time and the value from the database table are acquired every second and displayed on the page, but the processing in this timer stops after a certain amount of time.
Although it is not measured accurately, the processing in the timer stops after about 10 to 30 minutes.
However, when I look at the debug tool of the browser, there is no error, and there is no error on visual studio.
I searched for an example like this but couldn't find it and couldn't determine what was causing it.
I want the processing in this timer to work until I explicitly specify it as stopped.
What could be the cause of this?
I'm in trouble because I don't know the cause.
Please give me some advice.
Thank you.
private string Time { get; set; }
protected override void OnInitialized()
{
var timer = new System.Threading.Timer((_) =>
{
Time = DateTime.Now.ToString();
InvokeAsync(() =>
{
// Get the value from the database
databaseValue = TimerProcessGetValue();
StateHasChanged();
});
}, null, 0, 1000);
base.OnInitialized();
}
Process called in Timer
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using(DbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
postscript
The full text of the target component.
PageRefleshTest.cs
#page "/refresh-ui-manually"
#using System.Threading;
#using TestBlazorServer.Models;
#using Microsoft.EntityFrameworkCore;
#inject IDbContextFactory<SQLbeginnerContext> DbFactory
<h1>#Time</h1>
<h1>TestValue:#databaseValue</h1>
private string Time { get; set; }
private int? databaseValue { get; set; }
protected override void OnInitialized()
{
var timer = new System.Threading.Timer((_) =>
{
Time = DateTime.Now.ToString();
InvokeAsync(() =>
{
// Get the value from the database
databaseValue = TimerProcessGetValue().Result;
StateHasChanged();
});
}, null, 0, 1000);
base.OnInitialized();
}
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
Implemented using System.Timers.Timer
#page "/Refresh2"
#inject IDbContextFactory<SQLbeginnerContext> DbFactory
#using TestBlazorServer.Models
#using Microsoft.EntityFrameworkCore
#using System.Timers
#implements IDisposable
<h3>PageRefresh2</h3>
<h1>#Time</h1>
<h1>#currentCount</h1>
<h1>TestValue:#databaseValue/h1>
#code {
private int currentCount = 0;
private Timer timer2 = new(1000);
private string Time { get; set; }
private int? databaseValue { get; set; }
protected override void OnInitialized()
{
timer2.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer2.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
Time = DateTime.Now.ToString();
databaseValue = TimerProcessGetValue().Result;
StateHasChanged();
});
}
public void Dispose() => timer2.Dispose();
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
await using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
}
It could of course be from the SignalR connection failing. That should reconnect however.
The main suspect here is .Result. You should always avoid .Wait() and .Result in async code. Aslo, your DB method is not really async, that might play a role too.
But that 'half hour' could also come from the Garbage collector throwing out the Timer object. See the purple Note on this page. When you have a Timer you should anchor it in a field and Dispose() it.
The next step is to make the eventhandler an async void, this is one of the very few cases that is called for. And then only Invoke the StatehasChanged call.
#implements IDisposable
...
#code {
System.Threading.Timer _timer; // use a private field
protected override void OnInitialized()
{
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
databaseValue = await TimerProcessGetValue();
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
// base.OnInitialized(); -- not needed
}
public void Dispose()
{
_timer?.Dispose();
}
}
And make the Db action actually async:
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue =
(await dbContext.TestTable.SingleAsync(x => x.Id == 1))
.TestValue;
}
return timerProcessValue;
}
Only you can test this in-situ, let us know if it works.

Cleaning up a Task in an AsyncDisposable (VSTHRD003)

I have a class which creates a Task which runs during its whole lifetime, that is, until Dispose() is called on it:
In the constructor I call:
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
The way I currently do it (which I am also not sure is the right way) is cancelling the CancellationToken, and waiting on the task.
public void Dispose()
{
if (_isDisposed)
{
return;
}
_workerCancellation.Cancel();
_worker.GetAwaiter().GetResult();
_isDisposed = true;
}
When I do the same in the AsyncDispose method like so:
public async ValueTask DisposeAsync()
{
await _worker;
}
I get this warning
How do I correctly dispose of such a worker? Thanks!
As requested, here is the full code of what I am trying to do:
public sealed class ActiveObjectWrapper<T, TS> : IAsyncDisposable
{
private bool _isDisposed = false;
private const int DefaultQueueCapacity = 1024;
private readonly Task _worker;
private readonly CancellationTokenSource _workerCancellation;
private readonly Channel<(T, TaskCompletionSource<TS>)> _taskQueue;
private readonly Func<T, TS> _onReceive;
public ActiveObjectWrapper(Func<T, TS> onReceive, int? queueCapacity = null)
{
_onReceive = onReceive;
_taskQueue = Channel.CreateBounded<(T, TaskCompletionSource<TS>)>(queueCapacity ?? DefaultQueueCapacity);
_workerCancellation = new CancellationTokenSource();
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
await foreach (var (value, taskCompletionSource) in _taskQueue.Reader.ReadAllAsync(cancellationToken))
{
try
{
var result = _onReceive(value); // todo: do I need to propagate the cancellation token?
taskCompletionSource.SetResult(result);
}
catch (Exception exception)
{
taskCompletionSource.SetException(exception);
}
}
}
public async Task<TS> EnqueueAsync(T value)
{
// see: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
var completionSource = new TaskCompletionSource<TS>(TaskCreationOptions.RunContinuationsAsynchronously);
await _taskQueue.Writer.WriteAsync((value, completionSource));
return await completionSource.Task;
}
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_taskQueue.Writer.Complete();
_workerCancellation.Cancel();
await _worker;
_isDisposed = true;
}
}
This is the pattern that I use when implementing both IDisposabe and IDisposeableAsync. It isn't strictly compliant with the .Net recommendations. I found that implementing DisposeAsyncCore() was unnecessary as my classes are sealed.
public void Dispose()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
Worker.GetAwaiter().GetResult();
}
public void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
if (disposing)
{
lock (isDisposing)
{
if (isDisposed)
{
return;
}
Cts.Cancel();
Cts.Dispose();
isDisposed = true;
}
}
}
public async ValueTask DisposeAsync()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
await Worker;
}
This looks like an attempt to build a TransformBlock<TIn,TOut> on top of Channels. Using a Channel properly wouldn't generate such a warning. There's no reason to use a single processing task, or store it in a field.
Using Blocks
First, the equivalent code using a TransformBlock<Tin,TOut> would be:
var block=new TransformBlock<TIn,TOut>(msgIn=>func(msgIn));
foreach(....)
{
block.Post(someMessage);
}
To stop it, block.Complete() would be enough. Any pending messages would still be processed. A TransformBlock is meant to forward its output to other blocks, eg an ActionBlock or BufferBlock.
var finalBlock=new ActionBlock<TOut>(msgOut=>Console.WriteLine(msgOut));
block.LinkTo(finalBlock,new DataflowLinkOptions{PropagateCompletion = true});
...
block.Complete();
await finalBlock.Completion;
Using Channels
Doing something similar with channels doesn't need explicit classes, or Enqueue/Dequeue methods. Channels are build with separate ChannelReader, ChannelWriter interfaces to make it easier to control ownership, concurrency and completion.
A similar pipeline using channels would require only some methods:
ChannelReader<string> FolderToChannel(string path,CancellationToken token=default)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
foreach(var path in Directory.EnumerateFiles(path))
{
await _writer.SendAsync(path);
if (token.CancellationRequested)
{
return;
}
}
},token).ContinueWith(t=>_writer.TryComplete(t.Exception));
return channel;
}
This produces a reader that can be passed to a processing method. One that could generate another reader with the results:
static ChannelReader<MyClass> ParseFile(this ChannelReader<string> reader,CancellationToken token)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
await foreach(var path in reader.ReadAllAsync(token))
{
var json= await File.ReadAllTextAsync(path);
var dto= JsonSerializer.DeserializeObject<MyClass>(json);
await _writer.SendAsync(dto);
}
},token).ContineWith(t=>writer.TryComplete(t.Exception);
return channel;
}
And a final step that only consumes a channel:
static async Task LogIt(this ChannelReader<MyClass> reader,CancellationToken token)
{
await Task.Run(async ()=>{
await foreach(var dto in reader.ReadAllAsync(token))
{
Console.WriteLine(dto);
}
},token);
}
The three steps can be combined very easily:
var cts=new CancellationTokenSource();
var complete=FolderToChannel(somePath,cts.Token)
.ParseFile(cts.Token)
.LogIt(cts.Token);
await complete;
By encapsulating the channel itself and the processing in a method there's no ambiguity about who owns the channel, who is responsible for completion or cancellation

Blazor Server Problem that page update by Timer stops

I have written a program that updates pages regularly with the Blazor server app.
When this program is executed, the current time and the value from the database table are acquired every second and displayed on the page, but the processing in this timer stops after a certain amount of time.
Although it is not measured accurately, the processing in the timer stops after about 10 to 30 minutes.
However, when I look at the debug tool of the browser, there is no error, and there is no error on visual studio.
I searched for an example like this but couldn't find it and couldn't determine what was causing it.
I want the processing in this timer to work until I explicitly specify it as stopped.
What could be the cause of this?
I'm in trouble because I don't know the cause.
Please give me some advice.
Thank you.
private string Time { get; set; }
protected override void OnInitialized()
{
var timer = new System.Threading.Timer((_) =>
{
Time = DateTime.Now.ToString();
InvokeAsync(() =>
{
// Get the value from the database
databaseValue = TimerProcessGetValue();
StateHasChanged();
});
}, null, 0, 1000);
base.OnInitialized();
}
Process called in Timer
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using(DbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
postscript
The full text of the target component.
PageRefleshTest.cs
#page "/refresh-ui-manually"
#using System.Threading;
#using TestBlazorServer.Models;
#using Microsoft.EntityFrameworkCore;
#inject IDbContextFactory<SQLbeginnerContext> DbFactory
<h1>#Time</h1>
<h1>TestValue:#databaseValue</h1>
private string Time { get; set; }
private int? databaseValue { get; set; }
protected override void OnInitialized()
{
var timer = new System.Threading.Timer((_) =>
{
Time = DateTime.Now.ToString();
InvokeAsync(() =>
{
// Get the value from the database
databaseValue = TimerProcessGetValue().Result;
StateHasChanged();
});
}, null, 0, 1000);
base.OnInitialized();
}
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
Implemented using System.Timers.Timer
#page "/Refresh2"
#inject IDbContextFactory<SQLbeginnerContext> DbFactory
#using TestBlazorServer.Models
#using Microsoft.EntityFrameworkCore
#using System.Timers
#implements IDisposable
<h3>PageRefresh2</h3>
<h1>#Time</h1>
<h1>#currentCount</h1>
<h1>TestValue:#databaseValue/h1>
#code {
private int currentCount = 0;
private Timer timer2 = new(1000);
private string Time { get; set; }
private int? databaseValue { get; set; }
protected override void OnInitialized()
{
timer2.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer2.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
Time = DateTime.Now.ToString();
databaseValue = TimerProcessGetValue().Result;
StateHasChanged();
});
}
public void Dispose() => timer2.Dispose();
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
await using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue = dbContext.TestTable.Single(x => x.Id == 1).TestValue;
}
return timerProcessValue;
}
}
It could of course be from the SignalR connection failing. That should reconnect however.
The main suspect here is .Result. You should always avoid .Wait() and .Result in async code. Aslo, your DB method is not really async, that might play a role too.
But that 'half hour' could also come from the Garbage collector throwing out the Timer object. See the purple Note on this page. When you have a Timer you should anchor it in a field and Dispose() it.
The next step is to make the eventhandler an async void, this is one of the very few cases that is called for. And then only Invoke the StatehasChanged call.
#implements IDisposable
...
#code {
System.Threading.Timer _timer; // use a private field
protected override void OnInitialized()
{
_timer = new System.Threading.Timer(async (_) =>
{
Time = DateTime.Now.ToString();
databaseValue = await TimerProcessGetValue();
await InvokeAsync(StateHasChanged);
}, null, 0, 1000);
// base.OnInitialized(); -- not needed
}
public void Dispose()
{
_timer?.Dispose();
}
}
And make the Db action actually async:
public async Task<int?> TimerProcessGetValue()
{
int? timerProcessValue;
using (var dbContext = DbFactory.CreateDbContext())
{
timerProcessValue =
(await dbContext.TestTable.SingleAsync(x => x.Id == 1))
.TestValue;
}
return timerProcessValue;
}
Only you can test this in-situ, let us know if it works.

What is correct way to combine long-running tasks with async / await pattern?

I have a "High-Precision" timer class that I need to be able to be start, stop & pause / resume. To do this, I'm tying together a couple of different examples I found on the internet, but I'm not sure if I'm using Tasks with asnyc / await correctly.
Here is my relevant code:
//based on http://haukcode.wordpress.com/2013/01/29/high-precision-timer-in-netc/
public class HighPrecisionTimer : IDisposable
{
Task _task;
CancellationTokenSource _cancelSource;
//based on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
PauseTokenSource _pauseSource;
Stopwatch _watch;
Stopwatch Watch { get { return _watch ?? (_watch = Stopwatch.StartNew()); } }
public bool IsPaused
{
get { return _pauseSource != null && _pauseSource.IsPaused; }
private set
{
if (value)
{
_pauseSource = new PauseTokenSource();
}
else
{
_pauseSource.IsPaused = false;
}
}
}
public bool IsRunning { get { return !IsPaused && _task != null && _task.Status == TaskStatus.Running; } }
public void Start()
{
if (IsPaused)
{
IsPaused = false;
}
else if (!IsRunning)
{
_cancelSource = new CancellationTokenSource();
_task = new Task(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning);
_task.Start();
}
}
public void Stop()
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
public void Pause()
{
if (!IsPaused)
{
if (_watch != null)
{
_watch.Stop();
}
}
IsPaused = !IsPaused;
}
async void ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
// DO CUSTOM TIMER STUFF...
}
if (_watch != null)
{
_watch.Stop();
_watch = null;
}
_cancelSource = null;
_pauseSource = null;
}
public void Dispose()
{
if (IsRunning)
{
_cancelSource.Cancel();
}
}
}
Can anyone please take a look and provide me some pointers on whether I'm doing this correctly?
UPDATE
I have tried modifying my code per Noseratio's comments below, but I still cannot figure out the syntax. Every attempt to pass the ExecuteAsync() method to either TaskFactory.StartNew or Task.Run, results in a compilation error like the following:
"The call is ambiguous between the following methods or properties: TaskFactory.StartNew(Action, CancellationToken...) and TaskFactory.StartNew<Task>(Func<Task>, CancellationToken...)".
Finally, is there a way to specify the LongRunning TaskCreationOption without having to provide a TaskScheduler?
async **Task** ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//...
}
}
public void Start()
{
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, null);
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token);
//_task = Task.Run(ExecuteAsync, _cancelSource.Token);
}
UPDATE 2
I think I've narrowed this down, but still not sure about the correct syntax. Would this be the right way to create the task so that the consumer / calling code continues on, with the task spinning-up and starting on a new asynchronous thread?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Here are some points:
async void methods are only good for asynchronous event handlers (more info). Your async void ExecuteAsync() returns instantly (as soon as the code flow reaches await _pauseSource inside it). Essentially, your _task is in the completed state after that, while the rest of ExecuteAsync will be executed unobserved (because it's void). It may even not continue executing at all, depending on when your main thread (and thus, the process) terminates.
Given that, you should make it async Task ExecuteAsync(), and use Task.Run or Task.Factory.StartNew instead of new Task to start it. Because you want your task's action method be async, you'd be dealing with nested tasks here, i.e. Task<Task>, which Task.Run would automatically unwrap for you. More info can be found here and here.
PauseTokenSource takes the following approach (by design, AFAIU): the consumer side of the code (the one which calls Pause) actually only requests a pause, but doesn't synchronize on it. It will continue executing after Pause, even though the producer side may not have reached the awaiting state yet, i.e. await _pauseSource.Token.WaitWhilePausedAsync(). This may be ok for your app logic, but you should be aware of it. More info here.
[UPDATE] Below is the correct syntax for using Factory.StartNew. Note Task<Task> and task.Unwrap. Also note _task.Wait() in Stop, it's there to make sure the task has completed when Stop returns (in a way similar to Thread.Join). Also, TaskScheduler.Default is used to instruct Factory.StartNew to use the thread pool scheduler. This is important if your create your HighPrecisionTimer object from inside another task, which in turn was created on a thread with non-default synchronization context, e.g. a UI thread (more info here and here).
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class HighPrecisionTimer
{
Task _task;
CancellationTokenSource _cancelSource;
public void Start()
{
_cancelSource = new CancellationTokenSource();
Task<Task> task = Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: _cancelSource.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
_task = task.Unwrap();
}
public void Stop()
{
_cancelSource.Cancel(); // request the cancellation
_task.Wait(); // wait for the task to complete
}
async Task ExecuteAsync()
{
Console.WriteLine("Enter ExecuteAsync");
while (!_cancelSource.IsCancellationRequested)
{
await Task.Delay(42); // for testing
// DO CUSTOM TIMER STUFF...
}
Console.WriteLine("Exit ExecuteAsync");
}
}
class Program
{
public static void Main()
{
var highPrecisionTimer = new HighPrecisionTimer();
Console.WriteLine("Start timer");
highPrecisionTimer.Start();
Thread.Sleep(2000);
Console.WriteLine("Stop timer");
highPrecisionTimer.Stop();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
}
}
I'm adding code for running long running task (infinite with cancelation) with internal sub tasks:
Task StartLoop(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(async () => {
while (true)
{
if (cancellationToken.IsCancellationRequested)
break;
await _taskRunner.Handle(cancellationToken);
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
}
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}

A pattern to pause/resume an async task?

I have a mostly IO-bound continuous task (a background spellchecker talking to a spellcheck server). Sometimes, this task needs to be put on hold and resumed later, depending on the user activity.
While suspend/resume is essentially what async/await does, I've found little information on how to implement the actual pause/play logic for an asynchronous method. Is there a recommended pattern for this?
I've also looked at using Stephen Toub's AsyncManualResetEvent for this, but thought it might be an overkill.
Updated for 2019, I've recently had a chance to revisit this code, below is complete example as a console app (warning: PauseTokenSource needs good unit testing).
Note, in my case, the requirement was that when the consumer-side code (which requested the pause) would continue, the producer-side code should have already reached the paused state. Thus, by the time the UI is ready to reflect the paused state, all background activity is expected to have been already paused.
using System;
using System.Threading.Tasks;
using System.Threading;
namespace Console_19613444
{
class Program
{
// PauseTokenSource
public class PauseTokenSource
{
bool _paused = false;
bool _pauseRequested = false;
TaskCompletionSource<bool> _resumeRequestTcs;
TaskCompletionSource<bool> _pauseConfirmationTcs;
readonly SemaphoreSlim _stateAsyncLock = new SemaphoreSlim(1);
readonly SemaphoreSlim _pauseRequestAsyncLock = new SemaphoreSlim(1);
public PauseToken Token { get { return new PauseToken(this); } }
public async Task<bool> IsPaused(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
return _paused;
}
finally
{
_stateAsyncLock.Release();
}
}
public async Task ResumeAsync(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
if (!_paused)
{
return;
}
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
var resumeRequestTcs = _resumeRequestTcs;
_paused = false;
_pauseRequested = false;
_resumeRequestTcs = null;
_pauseConfirmationTcs = null;
resumeRequestTcs.TrySetResult(true);
}
finally
{
_pauseRequestAsyncLock.Release();
}
}
finally
{
_stateAsyncLock.Release();
}
}
public async Task PauseAsync(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
if (_paused)
{
return;
}
Task pauseConfirmationTask = null;
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
_pauseRequested = true;
_resumeRequestTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_pauseConfirmationTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
pauseConfirmationTask = WaitForPauseConfirmationAsync(token);
}
finally
{
_pauseRequestAsyncLock.Release();
}
await pauseConfirmationTask;
_paused = true;
}
finally
{
_stateAsyncLock.Release();
}
}
private async Task WaitForResumeRequestAsync(CancellationToken token)
{
using (token.Register(() => _resumeRequestTcs.TrySetCanceled(), useSynchronizationContext: false))
{
await _resumeRequestTcs.Task;
}
}
private async Task WaitForPauseConfirmationAsync(CancellationToken token)
{
using (token.Register(() => _pauseConfirmationTcs.TrySetCanceled(), useSynchronizationContext: false))
{
await _pauseConfirmationTcs.Task;
}
}
internal async Task PauseIfRequestedAsync(CancellationToken token = default(CancellationToken))
{
Task resumeRequestTask = null;
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
if (!_pauseRequested)
{
return;
}
resumeRequestTask = WaitForResumeRequestAsync(token);
_pauseConfirmationTcs.TrySetResult(true);
}
finally
{
_pauseRequestAsyncLock.Release();
}
await resumeRequestTask;
}
}
// PauseToken - consumer side
public struct PauseToken
{
readonly PauseTokenSource _source;
public PauseToken(PauseTokenSource source) { _source = source; }
public Task<bool> IsPaused() { return _source.IsPaused(); }
public Task PauseIfRequestedAsync(CancellationToken token = default(CancellationToken))
{
return _source.PauseIfRequestedAsync(token);
}
}
// Basic usage
public static async Task DoWorkAsync(PauseToken pause, CancellationToken token)
{
try
{
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Before await pause.PauseIfRequestedAsync()");
await pause.PauseIfRequestedAsync();
Console.WriteLine("After await pause.PauseIfRequestedAsync()");
await Task.Delay(1000);
}
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e);
throw;
}
}
static async Task Test(CancellationToken token)
{
var pts = new PauseTokenSource();
var task = DoWorkAsync(pts.Token, token);
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Press enter to pause...");
Console.ReadLine();
Console.WriteLine("Before pause requested");
await pts.PauseAsync();
Console.WriteLine("After pause requested, paused: " + await pts.IsPaused());
Console.WriteLine("Press enter to resume...");
Console.ReadLine();
Console.WriteLine("Before resume");
await pts.ResumeAsync();
Console.WriteLine("After resume");
}
}
static async Task Main()
{
await Test(CancellationToken.None);
}
}
}
AsyncManualResetEvent is exactly what you need, considering how messy your current code is. But a slightly better solution would be to use another approach from Stephen Toub: PauseToken. It works similarly to AsyncManualResetEvent, except its interface is made specifically for this purpose.
All the other answers seem either complicated or missing the mark when it comes to async/await programming by holding the thread which is CPU expensive and can lead to deadlocks. After lots of trial, error and many deadlocks, this finally worked for my high usage test.
var isWaiting = true;
while (isWaiting)
{
try
{
//A long delay is key here to prevent the task system from holding the thread.
//The cancellation token allows the work to resume with a notification
//from the CancellationTokenSource.
await Task.Delay(10000, cancellationToken);
}
catch (TaskCanceledException)
{
//Catch the cancellation and it turns into continuation
isWaiting = false;
}
}
it is works for me
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTest2
{
class Program
{
static ManualResetEvent mre = new ManualResetEvent(false);
static void Main(string[] args)
{
mre.Set();
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("________________");
mre.WaitOne();
}
} );
Thread.Sleep(10000);
mre.Reset();
Console.WriteLine("Task Paused");
Thread.Sleep(10000);
Console.WriteLine("Task Will Resume After 1 Second");
Thread.Sleep(1000);
mre.Set();
Thread.Sleep(10000);
mre.Reset();
Console.WriteLine("Task Paused");
Console.Read();
}
}
}
Ok, maybe this deserves an answer, but I'm not so familiar with C# and I don't have MonoDevelop here, and it's 3 o' clock AM, so please have pity.
I'm suggesting something like this
class Spellchecker
{
private CancellationTokenSource mustStop = null;
private volatile Task currentTask = null;
//TODO add other state variables as needed
public void StartSpellchecker()
{
if (currentTask != null)
{
/*
* A task is already running,
* you can either throw an exception
* or silently return
*/
}
mustStop = new CancellationTokenSource();
currentTask = SpellcheckAsync(mustStop.Token);
currentTask.Start();
}
private async Task SpellcheckAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested))
{
/*
* TODO perform spell check
* This method must be the only one accessing
* the spellcheck-related state variables
*/
}
currentTask = null;
}
public async Task StopSpellchecker()
{
if (currentTask == null)
{
/*
* There is no task running
* you can either throw an exception
* or silently return
*/
}
else
{
/*
* A CancelAfter(TimeSpan) method
* is also available, which might interest you
*/
mustStop.Cancel();
//Remove the following lines if you don't want to wait for the task to actually stop
var task = currentTask;
if (task != null)
{
await task;
}
}
}
}

Categories

Resources