Sharing variables between page and background task - c#

I have a pretty long task for my page, so I'm running it in a background task since I don't want it to block any postback events. Let's just assume this code looks something like this
Task task = new Task(() =>
{
for (;;)
{
Thread.Sleep(5000);
var x = (int)ViewState["progress"];
x++;
ViewState["progress"] = x;
}
});
task.Start();
As you can see, I'm storing the code in the ViewState. I have a progress bar on the page along with a timer that ticks every 5 seconds to perform a postback event. In this event, I'm updating the progress bar. The code looks something like this.
protected void Timer1_Tick(object sender, EventArgs e)
{
int perc = (int)ViewState["progress"];
//Progress bar code here
}
The progress bar wasn't updating so I tried debugging and for some reason, I'm getting entirely different values in both the cases. The timer's event gives me 0 all the time but it seems to be increasing in the loop. It's like there are two different instances of the ViewState
I've also tried locking the ViewState or using the global variable instead, but no luck.

What you have is a recipe for disaster. Thead.Sleep will suspend the thread which is very undesirable.
Disclaimer I do not recommend this technique!
You might consider using the slightly better
task = Task.Run(async () =>
{
for (var progress = 0; progress < 100; ++progress)
{
await Task.Delay(5000);
ViewState[nameof(progress)] = progress;
}
});
That said, it is still a recipe for disaster.

Related

How to keep cancelling the task until a condition is met (TaskCanceledException)

I want to call a method after some delay when an event is raised, but any subsequent events should "restart" this delay. Quick example to illustrate, the view should be updated when scrollbar position changes, but only 1 second after the user has finished scrolling.
Now I can see many ways of implementing that, but the most intuitive would be to use Task.Delay + ContinueWith + cancellation token. However, I am experiencing some issues, more precisely subsequent calls to my function cause the TaskCanceledException exception and I started to wonder how I could get rid of that. Here is my code:
private CancellationTokenSource? _cts;
private async void Update()
{
_cts?.Cancel();
_cts = new();
await Task.Delay(TimeSpan.FromSeconds(1), _cts.Token)
.ContinueWith(o => Debug.WriteLine("Update now!"),
TaskContinuationOptions.OnlyOnRanToCompletion);
}
I have found a workaround that works pretty nicely, but I would like to make the first idea work.
private CancellationTokenSource? _cts;
private CancellationTokenRegistration? _cancellationTokenRegistration;
private void Update()
{
_cancellationTokenRegistration?.Unregister();
_cts = new();
_cancellationTokenRegistration = _cts.Token.Register(() => Debug.WriteLine("Update now!"));
_cts.CancelAfter(1000);
}
You should consider using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq;.
You didn't say hat UI you're using, so for Windows Forms also add System.Reactive.Windows.Forms and for WPF System.Reactive.Windows.Threading.
Then you can do this:
Panel panel = new Panel(); // assuming this is a scrollable control
IObservable<EventPattern<ScrollEventArgs>> query =
Observable
.FromEventPattern<ScrollEventHandler, ScrollEventArgs>(
h => panel.Scroll += h,
h => panel.Scroll -= h)
.Select(sea => Observable.Timer(TimeSpan.FromSeconds(1.0)).Select(_ => sea))
.Switch();
IDisposable subscription = query.Subscribe(sea => Console.WriteLine("Hello"));
The query is firing for every Scroll event and starts a one second timer. The Switch operator watches for every Timer produces and only connects to the latest one produced, thus ignoring the previous Scroll events.
And that's it.
After scrolling has a 1 second pause the word "Hello" is written to the console. If you begin scrolling again then after every further 1 second pause it fires again.
In my own experience I've dealt with lots of scenarios just like the one you describe, e.g. update something one second after the mouse stops moving etc.
For a long time I would do timer restarts just the way you describe, by cancelling an old task and starting a new one. But I never really liked how messy that was, so I came up with an alternative that I use in production code. Long-term it has proven quite reliable. It takes advantage of the captured context associated with a task. Multiple instances of TaskCanceledException no longer occur.
class WatchDogTimer
{
int _wdtCount = 0;
public TimeSpan Interval { get; set; } = TimeSpan.FromSeconds(1);
public void Restart(Action onRanToCompletion)
{
_wdtCount++;
var capturedCount = _wdtCount;
Task
.Delay(Interval)
.GetAwaiter()
.OnCompleted(() =>
{
// If the 'captured' localCount has not changed after awaiting the Interval,
// it indicates that no new 'bones' have been thrown during that interval.
if (capturedCount.Equals(_wdtCount))
{
onRanToCompletion();
}
});
}
}
Another nice perk is that it doesn't rely on platform timers and works just as well in iOS/Android as it does in WinForms/WPF.
For purposes of demonstration, this can be exercised in a quick console demo where the MockUpdateView() action is sent to the WDT 10 times at 500 ms intervals. It will only execute one time, 500 ms after the last restart is received.
static void Main(string[] args)
{
Console.Title = "Test WDT";
var wdt = new WatchDogTimer { Interval = TimeSpan.FromMilliseconds(500) };
Console.WriteLine(DateTime.Now.ToLongTimeString());
// "Update view 500 ms after the last restart."
for (int i = 0; i < 10; i++)
{
wdt.Restart(onRanToCompletion: ()=>MockUpdateView());
Thread.Sleep(TimeSpan.FromMilliseconds(500));
}
Console.ReadKey();
}
static void MockUpdateView()
{
Console.WriteLine($"Update now! WDT expired {DateTime.Now.ToLongTimeString()}");
}
}
So, with 500 ms times 10 restarts this verifies one event at 5 seconds from the start.
You can combine a state variable and a delay to avoid messing with timers or task cancelation. This is far simpler IMO.
Add this state variable to your class/form:
private DateTime _nextRefresh = DateTime.MaxValue;
And here's how you refresh:
private async void Update()
{
await RefreshInOneSecond();
}
private async Task RefreshInOneSecond()
{
_nextRefresh = DateTime.Now.AddSeconds(1);
await Task.Delay(1000);
if (_nextRefresh <= DateTime.Now)
{
_nextRefresh = DateTime.MaxValue;
Refresh();
}
}
If you call RefreshInOneSecond repeatedly, it pushes out the _nextRefresh timestamp until later, so any refreshes already in flight will do nothing.
Demo on DotNetFiddle
One approach is to create a timer and reset this whenever the user does something. For example using System.Timers.Timer
timer = new Timer(1000);
timer.SynchronizingObject = myControl; // Needs a winforms object for synchronization
timer.Elapsed += OnElapsed;
timer.Start(); // Don't forget to stop the timer whenever you are done
...
private void OnUserUpdate(){
timer.Interval = 1000; // Setting the interval will reset the timer
}
There are multiple timers to chose from, I believe the same pattern is possible with the other timers. DispatchTimer might be most suitable if you use WPF.
Note that both System.Timers.Timer and Task.Delay uses System.Threading.Timer in the background. It is possible to use this directly, just call the .Change method to reset it. But be aware that this raises the event on a taskpool thread, so you need to provide your own synchronization.
I implemented the same scenario in a JavaScript application using Timer. I believe it's the same in the .NET world. Anyway handling this use-case when the user calls a method repeatedly with Task.Delay() will put more pressure on GC & thread pool
var timer = new Timer()
{
Enabled = true,
Interval = TimeSpan.FromSeconds(5).TotalMilliseconds,
};
timer.Elapsed += (sender, eventArgs) =>
{
timer.Stop();
// do stuff
}
void OnKeyUp()
{
timer.Stop();
timer.Start();
}

C# - ' Dispatcher.BeginInvoke(new Action(() => ' doesn't work within Winforms. CS0120 [duplicate]

I am trying to understand better how can I update a windows forms progress bar from an async operation but I am getting some unexpected behavior from that.
Basically I am having a button which should after is being clicked to update a progress bar and then set it back to 0 once the progress bar gets 100% updated.
This is my code:
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync().ContinueWith((prevTask) =>
{
prevTask.Wait();
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
});
}
private static async Task ExecuteMethodAsync(IProgress<double> progress = null)
{
double percentComplete = 0;
bool done = false;
while (!done)
{
if (progress != null)
{
progress.Report(percentComplete);
}
percentComplete += 10;
if(percentComplete == 100)
{
done = true;
}
}
}
private async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); };
await ExecuteMethodAsync(progress);
}
Having this implementation the progress bar is not being updated at all even if I call "Wait()" on the operation that should update the value of the progress bar.
If i remove this part of code:
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
the progress bar gets updated but it remains all the time like that, and I want to set it back to 0 once it was entirely filled so that I can update it again when I click again the button.
Could someone please explain me what am I doing wrong ?
One of the reasons async-await syntax was invented because it was difficult to follow the sequence of instructions when tasks were concatenated using functions like ContinueWith.
If you use async-await it is seldom necessary to use statements like ContinueWith. After an await, the thread already continues with the statements after the await.
If the button is clicked, you want to call ExcecuteMethodAsync. This function takes an IProgress, because it wants to report progress regularly. You want to call this function asynchronously, so whenever the function has to wait for something, it doesn't really wait, but returns control to you so you could do other things instead of really waiting, until you encounter an await, in which case your caller continues processing until he encounters an await, etc.
The nice thing with async-await is that the thread that continues after your call to an async function has the same context as the calling thread. This means that you can regard it as your original thread. No InvokeRequired, no need to protect data with mutexes etc.
Your function could be simplified as follows:
async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += OnProgressReported;
await ExecuteMethodAsync(progress);
}
private void OnProgressReported(object sender, ...)
{
// because this thread has the context of the main thread no InvokeRequired!
this.progressBar1.Increment(...);
}
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync();
}
So when the button is clicked, CallMethodAsync is called. This function will create A Progress object and subscribes on its Report event. Note that this is still your UI-thread. Then it calls ExecuteMethodAsync, which will regularly raise event Report, which is handled by OnProgressReported.
Because ExecuteMethodAsync is async, you can be sure there is somewhere an await in it. This means that whenever it has to await, control returns to the caller, which is CallMethodAsync, until is encounters an await, which in this case is immediately.
Control goes up the call stack to the caller, which is button1_click, where it immediately encounters an await, so control goes up the call stack, etc.
All these controls have the same context: it is as if they are the same thread.
An article that helped me a lot to understand async-await is this interview with Eric Lippert. Search somewhere in the middle for async await
Another articel that helped me a lot to learn good practices were this article by the ever so helpful Stephen Cleary and Async/Await - Best Practices in Asynchronous Programming also by Stephen Cleary
Your issue is happening because ExecuteMethodAsync(...) is not actually asynchronous.
Add the following before the while loop to make it asynchronous
await Task.Delay(1);
or enclose some synchronous portion of code (e.g. the while loop) into a:
await Task.Run(() => { ... });
or (the best one), add the following at the beginning of the function:
await Task.Yield(); // Make us async right away

Using async/await: await returns too early

I have a simly Windows Forms app with just a button and a progressbar on it.
Then I have this code:
private async void buttonStart_Click(object sender, EventArgs e)
{
progressBar.Minimum = 0;
progressBar.Maximum = 5;
progressBar.Step = 1;
progressBar.Value = 0;
await ConvertFiles();
MessageBox.Show("ok");
}
private async Task ConvertFiles()
{
await Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
System.Threading.Thread.Sleep(1000);
Invoke(new Action(() => progressBar.PerformStep()));
}
});
}
The await ConvertFiles(); returns too early, the ok messagebox already appears at about 80% progress.
What am I doing wrong?
The problem you are experiencing is not related to the async/await, which you use correctly. The await is not returning too early, just the progress bar updates too late. In other words, this is a progress bar control specific problem described in a several threads - Disabling .NET progressbar animation when changing value?, Disable WinForms ProgressBar animation, The RunWorkerCompleted is triggered before the progressbar reaches 100% etc. You can use one of the workarounds provided in those threads.
Just to be safe why not move the
MessageBox.Show("ok");
into a Continuewith so:
await ConvertFiles().ContinueWith((t) => { MessageBox.Show("ok"); });
this makes sure it only runs when the task is complete

Custom ProgressBar Indicator not showing up before time consuming action

I implemented the custom progressbar indicator in my Windows Phone 8 project. It works fine if I try to toggle the indicator with a button. But of course I want it to show up while I perform time consuming actions (filling a list with many items). But as it blocks the UI the progressbar indicator doesn't show up before the action but only afterwards. I tried .UpdateLayout() on the indicator itself and the whole page before performing modifications to the list but none of it worked.
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
// add ~100 list items
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
Is there any other way to do this?
You could offload your time consuming work to a new task and add a continuation to set progress bar visibility at the end. Here i'm using the Task Parallel Library to achieve this:
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
Task.Run(() =>
{
// Do CPU intensive work
}).ContinueWith(task =>
{
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
}, TaskScheduler.FromCurrentSynchronizationContext());
You should run your heavy job asynchronously (more about async at MSDN and at the Stephen Cleary Blog) - so that it won't block UI.
The very simple example where you have a ProgressBar and a heavy Task which will inform PBar about its progress can look like this: (I've subscribed the start of the method to Button Click)
private async void StartBtn_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>( (p) =>
{
progresPB.Value = p;
});
await DoSomething(progress); // start asynchronously Task with progress indication
}
private Task<bool> DoSomething(IProgress<double> progress)
{
TaskCompletionSource<bool> taskComplete = new TaskCompletionSource<bool>();
// run your heavy task asynchronous
Task.Run(async () =>
{
for (int i = 0; i < 10; i++) // work divided into parts
{
await Task.Delay(1000); // some heavy work
progress.Report((double)i / 10);
}
taskComplete.TrySetResult(true);
});
return taskComplete.Task;
}

Show progress only if a background operation is long

I'm developing a C# operation and I would like to show a modal progress dialog, but only when an operation will be long (for example, more than 3 seconds). I execute my operations in a background thread.
The problem is that I don't know in advance whether the operation will be long or short.
Some software as IntelliJ has a timer aproach. If the operation takes more than x time, then show a dialog then.
What do you think that is a good pattern to implement this?
Wait the UI thread with a timer, and show dialog there?
Must I DoEvents() when I show the dialog?
Here's what I'd do:
1) Use a BackgroundWorker.
2) In before you call the method RunWorkerAsync, store the current time in a variable.
3) In the DoWork event, you'll need to call ReportProgress. In the ProgressChanged event, check to see if the time has elapsed greater than three seconds. If so, show dialog.
Here is a MSDN example for the BackgroundWorker: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
Note: In general, I agree with Ramhound's comment. Just always display the progress. But if you're not using BackgroundWorker, I would start using it. It'll make your life easier.
I will go with the first choice here with some modifications:
First run the possible long running operation in different thread.
Then run a different thread to check the first one status by a wait handle with timeout to wait it for finish. if the time out triggers there show the progress bar.
Something like:
private ManualResetEvent _finishLoadingNotifier = new ManualResetEvent(false);
private const int ShowProgressTimeOut = 1000 * 3;//3 seconds
private void YourLongOperation()
{
....
_finishLoadingNotifier.Set();//after finish your work
}
private void StartProgressIfNeededThread()
{
int result = WaitHandle.WaitAny(new WaitHandle[] { _finishLoadingNotifier }, ShowProgressTimeOut);
if (result > 1)
{
//show the progress bar.
}
}
Assuming you have a DoPossiblyLongOperation(), ShowProgressDialog() and HideProgressDialog() methods, you could use the TPL to do the heavy lifting for you:
var longOperation = new Task(DoPossiblyLongOperation).ContinueWith(() => myProgressDialog.Invoke(new Action(HideProgressDialog)));
if (Task.WaitAny(longOperation, new Task(() => Thread.Sleep(3000))) == 1)
ShowProgressDialog();
I would keep the progress dialog separate from the background activity, to separate my UI logic from the rest of the application. So the sequence would be (This is essentially the same as what IntelliJ does):
UI starts the background operation (in a BackgroundWorker) and set up a timer for X seconds
When the timer expires UI shows the progress dialog (if the background task is still running)
When the background task completes the timer is cancelled and the dialog (if any) is closed
Using a timer instead of a separate thread is more resource-efficient.
Recommended non-blocking solution and no new Threads:
try
{
var t = DoLongProcessAsync();
if (await Task.WhenAny(t, Task.Delay(1000)) != t) ShowProgress();
await t;
}
finally
{
HideProgress();
}
I got the idea from Jalal Said answer. I required the need to timeout or cancel the progress display. Instead of passing an additional parameter (cancellation token handle) to the WaitAny I changed the design to depend on Task.Delay()
private const int ShowProgressTimeOut = 750;//750 ms seconds
public static void Report(CancellationTokenSource cts)
{
Task.Run(async () =>
{
await Task.Delay(ShowProgressTimeOut);
if (!cts.IsCancellationRequested)
{
// Report progress
}
});
}
Use it like so;
private async Task YourLongOperation()
{
CancellationTokenSource cts = new CancellationTokenSource();
try
{
// Long running task on background thread
await Task.Run(() => {
Report(cts);
// Do work
cts.Cancel();
});
}
catch (Exception ex) { }
finally {cts.Cancel();}
}

Categories

Resources