Allow user to immediately cancel long-running WPF operation from button - c#

In a WPF app, we have a button that the user can click to trigger a list of videos to be loaded into VLC Media Player:
<Button Content="{Binding RotatorButtonLabel}" Command="{Binding RotateVideosCommand}" />
In the view model MainWindowVm, we have the command to handle the button click:
public ICommand RotateVideosCommand => new RelayCommand(RotateVideos);
private void RotateVideos()
{
IsRotatorActive = !IsRotatorActive;
RotatorButtonLabel = IsRotatorActive
? "Stop Rotator"
: "Rotate Videos";
_rotatorVm = new RotatorVm
{
ImageVms = ImagesView.Cast<ImageVm>().ToList(),
IsRotatorActive = IsRotatorActive
};
// This fires off a new thread to run the rotator, otherwise the UI freezes.
Task.Run(() => Messenger.Default.Send(rotatorVm, "LaunchRotator"));
}
Note in the above command handler, we use the MVVM Light Toolkit's Messenger to tell the code-behind to launch the rotator.
Now in the MainWindow.xaml.cs, we have the following c'tor:
private CancellationTokenSource _cancellationTokenSource = null;
private CancellationToken _cancellationToken;
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<RotatorVm>(this, "LaunchRotator", LaunchRotator);
// Other logic...
}
And then this what the above LaunchRotator calls:
private void LaunchRotator(RotatorVm rotatorVm)
{
if (_cancellationToken.IsCancellationRequested)
{
_cancellationTokenSource.Dispose();
}
if (_cancellationTokenSource == null || _cancellationToken.IsCancellationRequested)
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
}
if (!rotatorVm.IsRotatorActive)
{
_cancellationTokenSource.Cancel();
return;
}
RotateVideos();
}
private void RotateVideos()
{
while (true)
{
if (_cancellationToken.IsCancellationRequested)
{
return;
}
// This is to simplify the code and simulate work.
Thread.Sleep(5000);
}
}
If I click the "Stop Rotator" button, it can take a few seconds for the code to hit the next iteration of the while loop and read the IsCancellationRequested. How do I make it stop immediately in this scenario?
I've looked at this example, but it's assuming the task and the activities are all in one class; here, I have a view model and a code-behind. Thank you.

You can't (in a practical way) and shouldn't.
If you want to stop work in another thread then the proper way is to signal the thread (like you've done) and allow the thread to stop on it's own. Since your example workload is a Thread.Sleep(5000), once it's hit, that thread cannot do anything else until the sleep has expired. In other words, you can signal the thread properly and if it is sleeping, it will live until the sleep is complete, and then it will check the signal again.
Options:
In this case you can pass the Token to the simulated workload
using Task.Wait(5000, token) and using this instead of
Thread.Sleep(5000). This way the simulated work can also be
canceled.
For real work; you have to use your best judgement to test the signal
when and where it looks fair so that you're not waiting a long period
of time to return when cancel is signaled. Just note that making the caller wait while the
Thread or Task properly ends itself is the best way to do
it. This is why Thread.Abort gets so much criticism and is
discouraged.
Another option is to fire and forget. You can call Cancel() on the token source and then move on without waiting for the Task or Thread to complete. You have to design with this in mind but it's practical.

Related

cancelling a backgroundworker with while loop

i know the common ways of cancelling a backgroundworker using eventwaithandles...
but i wanna know is that right to use a while loop to trap and pause working of a backgroundworker ? i coded like this :
Bool stop = false;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
progressBar1.Minimum = 0;
progressBar1.Maximum = 100000;
progressBar1.Value = 0;
for (int i = 0; i < 100000; i++)
{
progressBar1.Value++;
if (i == 50000)
stop = true;
while (stop)
{ }
}
}
private void button1_Click(object sender, EventArgs e)
{
stop = !stop;
}
Did you try it? What happened? Was it what you wanted to happen? Did you notice your computer's fans speeding up, to handle all the heat from your CPU in a tight, "do-nothing" loop?
Fact is, you should not "pause" a background task in the first place; if you don't it to keep running, interrupt it. If you want to be able to resume later, provide a mechanism to allow that. Even having your thread blocked efficiently waiting on a WaitHandle object would be the wrong thing to do, because it wastes a thread pool thread.
The code you've posted here is about the worst way to implement "pausing". Instead of waiting on some synchronization object such as a WaitHandle, you have the current thread just loop without interrupting, constantly checking the value of a flag. Even ignoring the question of whether you're using volatile (the code example doesn't show that, but then it also wouldn't compile, so…), it's terrible to force a CPU core to do so much work and yet get nowhere.
Don't pause your BackgroundWorker.DoWork handler in the first place. Really. Just don't do that. But if you insist, then at least use some kind of waitable object instead of a "spin-wait" loop as in the example you've posted here.
Here's an example of how your code might work if you wanted to avoid altogether tying up a thread while "paused". First, don't use BackgroundWorker, because it doesn't have a graceful way to do this. Second, do use await…that does specifically what you want: it allows the current method to return, but without losing track of its progress. The method will resume executing when the thing it waited on indicates completion.
In the example below, I've tried to guess at what the code that calls RunWorkerAsync() looks like. Or rather, I just assumed you've got a button2, which when clicked you call that method to start your worker task. If this is not enough to get you pointed in the right direction, please improve your question by including a good, minimal, complete code example showing what you're actually doing.
// These fields will work together to provide a way for the thread to interrupt
// itself temporarily without actually using a thread at all.
private TaskCompletionSource<object> _pause;
private readonly object _pauseLock = new object();
private void button2_Click(object sender, DoWorkEventArgs e)
{
// Initialize ProgressBar. Note: in your version of the code, this was
// done in the DoWork event handler, but that handler isn't executed in
// the UI thread, and so accessing a UI object like progressBar1 is not
// a good idea. If you got away with it, you were lucky.
progressBar1.Minimum = 0;
progressBar1.Maximum = 100000;
progressBar1.Value = 0;
// This object will perform the duty of the BackgroundWorker's
// ProgressChanged event and ReportProgress() method.
Progress<int> progress = new Progress<int>(i => progressBar1.Value++);
// We do want the code to run in the background. Use Task.Run() to accomplish that
Task.Run(async () =>
{
for (int i = 0; i < 100000; i++)
{
progress.Report(i);
Task task = null;
// Locking ensures that the two threads which may be interacting
// with the _pause object do not interfere with each other.
lock (_pauseLock)
{
if (i == 50000)
{
// We want to pause. But it's possible we lost the race with
// the user, who also just pressed the pause button. So
// only allocate a new TCS if there isn't already one
if (_pause == null)
{
_pause = new TaskCompletionSource<object>();
}
}
// If by the time we get here, there's a TCS to wait on, then
// set our local variable for the Task to wait on. In this way
// we resolve any other race that might occur between the time
// we checked the _pause object and then later tried to wait on it
if (_pause != null)
{
task = _pause.Task;
}
}
if (task != null)
{
// This is the most important part: using "await" tells the method to
// return, but in a way that will allow execution to resume later.
// That is, when the TCS's Task transitions to the completed state,
// this method will resume executing, using any available thread
// in the thread pool.
await task;
// Once we resume execution here, reset the TCS, to allow the pause
// to go back to pausing again.
lock (_pauseLock)
{
_pause.Dispose();
_pause = null;
}
}
}
});
}
private void button1_Click(object sender, EventArgs e)
{
lock (_pauseLock)
{
// A bit more complicated than toggling a flag, granted. But it achieves
// the desirable goal.
if (_pause == null)
{
// Creates the object to wait on. The worker thread will look for
// this and wait if it exists.
_pause = new TaskCompletionSource<object>();
}
else if (!_pause.Task.IsCompleted)
{
// Giving the TCS a result causes its corresponding Task to transition
// to the completed state, releasing any code that might be waiting
// on it.
_pause.SetResult(null);
}
}
}
Note that the above is just as contrived as your original example. If all you had really was a simple single loop variable iterating from 0 to 100,000 and stopping halfway through, nothing nearly so complicated as the above would be required. You'd just store the loop variable in a data structure somewhere, exit the running task thread, and then when you want to resume, pass in the current loop variable value so the method can resume at the right index.
But I'm assuming your real-world example is not so simple. And the above strategy will work for any stateful processing, with the compiler doing all the heavy-lifting of storing away intermediate state for you.

Close all child WPF windows and terminate awaiting code

I am trying to implement a system for closing all modal and non-modal windows in a WPF application (with the exception of the main application window.) When these windows are closed, any code awaiting the result of a dialog should be abandoned.
So far, I've considered/attempted two strategies:
Close and restart the application.
Close all the windows and rely on task cancellation exceptions to abandon all code that was waiting for dialog results. (It bubbles up to the App level and then becomes handled.)
The first solution definitely gets the application to close and will suffice for automatic logout, but I am extremely uncomfortable with code that continues to execute after the dialog it was waiting for has been closed. Is there a nice way to stop the execution of that code?
The second solution has been working relatively well (calling code is aborted) but has one critical flaw: on occasion, some combination of modal and non-modal windows closing in quick succession will cause the application to lock up on a ShowDialog call. (At least, when you pause execution, that's where it ends up.) This is strange because breakpoints clearly demonstrate that the Closed event is being raised on all windows that I intend to close. The result that the end user sees is a login screen that cannot be clicked on but can be tabbed into. So strange! Attempts to dispatch the call at different priorities have been unsuccessful, but a Task.Delay for 100ms might have done the trick. (That's not a real solution, though.)
If each open popup is awaiting a TaskCompletionSource in the background, and, upon completion of the TCS, tries to use the dispatcher to invoke Close on itself, why would one (or more) of the dialogs still be blocking on ShowDialog, even after seeing the Closed event be raised? Is there a way to properly dispatch these calls to Close so they complete successfully? Do I need to be particular about the order in which the windows close?
Some pseudocode-C#-hybrid examples:
class PopupService
{
async Task<bool> ShowModalAsync(...)
{
create TaskCompletionSource, publish event with TCS in payload
await and return the TCS result
}
void ShowModal(...)
{
// method exists for historical purposes. code calling this should
// probably be made async-aware rather than relying on the blocking
// behavior of Window.ShowDialog
create TaskCompletionSource, publish event with TCS in payload
rethrow exceptions that are set on the Task after completion but do not await
}
void CloseAllWindows(...)
{
for every known TaskCompletionSource driving a popup interaction
tcs.TrySetCanceled()
}
}
class MainWindow : Window
{
void ShowModalEventHandler(...)
{
create a new PopupWindow and set the owner, content, etc.
var window = new PopupWindow(...) { ... };
...
window.ShowDialog();
}
}
class PopupWindow : Window
{
void LoadedEventHandler(...)
{
...
Task.Run(async () =>
{
try
await the task completion source
finally
Dispatcher.Invoke(Close, DispatcherPriority.Send);
});
register closing event handlers
...
}
void ClosedEventHandler(...)
{
if(we should do something with the TCS)
try set the TCS result so the popup service caller can continue
}
}
With Window.ShowDialog you create a nested Dispather message loop. With await, it's possible to "jump" on that inner loop and continue the logical execution of an async method there, e.g.:
var dialogTask = window.ShowDialogAsync();
// on the main message loop
await Task.Delay(1000);
// on the nested message loop
// ...
await dialogTask;
// expecting to be back on the main message loop
Now, if dialogTask gets completed via TaskCompletionSource before the corresponding Window.ShowDialog() call returns to the caller, the above code may still end up on the nested message loop, rather than on the main core message loop. E.g., this may happen if TaskCompletionSource.SetResult/TrySetCanceled is called inside the dialog's Window.Closed event handler, or right before/after Window.Close() call. This may create undesired reentrancy side effects, including deadlocks, too.
By looking at your pseudocode, it's hard to tell where the deadlock may be. What is worrying is that you're using Task.Run only to await a task that completes on the main UI thread, or to invoke a synchronous callback on the main UI thread from a pool thread (via Dispatcher.Invoke). You certainly shouldn't need Task.Run here.
I use the following version of ShowDialogAsync for a similar purpose. It makes sure any inner message loops started by nested ShowDialogAsync calls exit before this particular ShowDialogAsync task is complete:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
// testing ShowDialogAsync
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var modal1 = new Window { Title = "Modal 1" };
modal1.Loaded += async delegate
{
await Task.Delay(1000);
var modal2 = new Window { Title = "Modal 2" };
try
{
await modal2.ShowDialogAsync();
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal2.Title);
}
};
await Task.Delay(1000);
// close modal1 in 5s
// this would automatically close modal2
var cts = new CancellationTokenSource(5000);
try
{
await modal1.ShowDialogAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal1.Title);
}
}
}
/// <summary>
/// WindowExt
/// </summary>
public static class WindowExt
{
[ThreadStatic]
static CancellationToken s_currentToken = default(CancellationToken);
public static async Task<bool?> ShowDialogAsync(
this Window #this,
CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
var previousToken = s_currentToken;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(previousToken, token))
{
var currentToken = s_currentToken = cts.Token;
try
{
return await #this.Dispatcher.InvokeAsync(() =>
{
using (currentToken.Register(() =>
#this.Close(),
useSynchronizationContext: true))
{
try
{
var result = #this.ShowDialog();
currentToken.ThrowIfCancellationRequested();
return result;
}
finally
{
#this.Close();
}
}
}, DispatcherPriority.Normal, currentToken);
}
finally
{
s_currentToken = previousToken;
}
}
}
}
}
This allows you to cancel the most outer modal window via the associated CancelationToken, and that would automatically close any nested modal windows (those opened with ShowDialogAsync) and exit their corresponding message loops. So, your logical execution flow would end up on the correct outer message loop.
Note it still doesn't guarantee the correct logical order of closing multiple modal windows, if that matters. But it guarantees that the tasks returned by multiple nested ShowDialogAsync calls will get completed in the correct order.
I am not sure that this is gonna fix your issue, but in my case, I created extension methods to help mixing async code and window lifetime management. For example, you can create a ShowDialogAsync() that returns task that will complete when the window is actually closed. A CancellationToken can also be provided to close the dialog automatically if you request a cancellation.
public static class WindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window window, CancellationToken cancellationToken = new CancellationToken())
{
var completionSource = new TaskCompletionSource<bool?>();
window.Dispatcher.BeginInvoke(new Action(() =>
{
var result = window.ShowDialog();
// When dialog is closed, set the result to complete the returned task. If the task is already cancelled, it will be discarded.
completionSource.TrySetResult(result);
}));
if (cancellationToken.CanBeCanceled)
{
// Gets notified when cancellation is requested so that we can close window and cancel the returned task
cancellationToken.Register(() => window.Dispatcher.BeginInvoke(new Action(() =>
{
completionSource.TrySetCanceled();
window.Close();
})));
}
return completionSource.Task;
}
}
In your UI code, you would use the ShowDialogAsync() method like the following. As you can see, when the task is cancelled, the dialog is closed and an OperationCanceledException exception is thrown stopping the flow of your code.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
YourDialog dialog = new YourDialog();
CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await dialog.ShowDialogAsync(source.Token);
}
catch (OperationCanceledException ex)
{
MessageBox.Show("Operation was cancelled");
}
}
This is JUST for the first part of your issue (closing the windows).
if you do not need any of the results of the windows here is some simple code just to close all but the main window.
This is executing from my main window but you can change the if statement to look for your mainwindow instead if running from alternate area.
foreach(Window item in App.Current.Windows)
{
if(item!=this)
item.Close();
}
As for other threads I am unsure although as mentioned above if you have a list of handles to the threads then you should be able to traverse that as well and kill them.

Asynchronous Thread Lifetime & WPF UI Update

On button click I have fired call to StartContinuousThread which keeps polling the server every one second.
public class ThreadsWindow
{
CancellationTokenSource wtoken = new CancellationTokenSource();
private void btnStartTest_Click(object sender, RoutedEventArgs e)
{
StartContinuousThread();
}
void StartContinuousThread()
{
while(true)
{
var fact = Task.Factory.StartNew(() =>
{
CallServer();
Task.Delay(1000, wtoken.Token);
},
wtoken.Token);
}
}
}
StartContinuousThread starts executing, but btnStartTest_Click event handler finishes its execution.
How StartContinuousThread method would be able to update UI in this
case?
I wonder whether StartContinuousThread is also terminated with event handler, since there is no wait keyword for re-joining.
Please help!
If you goal is to poll the server every second you have a number of problem.
There is no loop. You execute the method once and then stop.
You create a task using Delay and then ignore it. You ought to be creating a continuation of that task to do the rest of the work to actually not do anything for a second.
Here is an implementation that addresses those issues:
private async Task StartContinuousThread(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
await Task.Run(() => CallServer());
await Task.Delay(1000, token);
}
}
Another possibility, especially if you're using an older version of C#, would be to use a timer to run some code every second.
As for updating the UI; you can do so freely anywhere outside of the call to Task.Run in this example. In your example you'd need to use some mechanism to marshal back to the UI thread, such as capturing the UI's synchronization context and posting to it.

Pause and Resume a Thread

I have this code to pause and resume a thread:
public partial class frmMain : Form
{
(...)
ManualResetEvent wait_handle = new ManualResetEvent(true);
(...)
}
private void frmMain_Shown(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(TheLoop));
}
private void TheLoop(object stateinfo)
{
bool hasInfo = true;
while (doLoop)
{
wait_handle.WaitOne();
bool hasLines = GetInfo();
if (hasLines)
{
//Consuming time Operation 1
System.Threading.Thread.Sleep(7000);
if (CurrentLine < line.Count - 1)
CurrentLine++;
else
{
bool hasInfo2 = GetInfo2();
if (hasInfo2)
{
//Consuming time Operation 2
System.Threading.Thread.Sleep(7000);
}
CurrentLine = 0;
}
}
else
System.Threading.Thread.Sleep(40000); //Wait to query again
}
}
private void btnPauseResume_Click(object sender, EventArgs e)
{
if (btnPauseResume.Text == "Pause")
{
btnPauseResume.Text = "Resume";
wait_handle.Reset();
}
else
{
btnPauseResume.Text = "Pause";
wait_handle.Set();
}
}
The code above shows a cycle information, it works find to pause and resume the "first consuming time operation" but doesn't work for the second one, if I press the button to pause the thread in the second consuming time operation, this one continues and when the first one appears again, then it pauses there.
What am I missing here?
Thx
Have you considered using a Background Worker instead since you are using WinForms? It would probably be easier than trying to 'Pause' a thread. You can check the CancellationPending property to see if a user has elected to cancel the operation. The link has a good sample to look at.
I have never seen someone pausing a thread. Create a delegate and event inside the class or method that you are executing on a separate threat. Execute that event whenever you wish to pause your thred.
There is not any reason that I can see that would prevent a second call to WaitOne from working if placed before the 2nd time consuming operation. Since you are using a ManualResetEvent the wait handle's state will persist until either Set or Reset is called. That means if you resume the thread by calling Set then both calls to WaitOne will pass through. Likewise, if you pause the thread by calling Reset then both calls to WaitOne will block. Of course, it will not be possible to predict where the worker thread will pause if there is more than one call to WaitOne.
Got it guys! the thing is where you put the WaitOne(). For instance, if I have a While Loop (like my example) if I put the wait before it, no matter how many times I hit the pause button, it won't stop the thread, it's logic since the loop already began, but if I put it at the end, then it will work.
Appreciated your help.

WPF / XAML: How do I execute threaded processes and prevent the main UI from being busy / freezing?

I have a XAML application that serves as the UI for an automation. The entire automation can take anywhere from 20-30 hours to fully execute so I created a Task class object that essentially wraps Thread methods (Start/Stop/Reset).
However, when I run the automation method under the Task object, the XAML UI is busy and I cannot interact with the other controls, including the Pause button which toggles the Thread.Set() flag.
There is another post
Prevent UI from freezing without additional threads
where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Any idea around this?
private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
{
btnPauseAutomation.IsEnabled = true;
Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
}
private void RunFullAutomation()
{
// do stuff that can take 20+ hours
// threaded so I can utilize a pause button (block)
}
class Task
{
private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
private Thread _thread;
private readonly Action _action;
public Task(Action action)
{
_action = action;
}
public void Start()
{
ThreadStart ts = new ThreadStart(DoDelegatedMethod);
_thread = new Thread(ts);
_thread.Start();
_thread.Priority = ThreadPriority.Lowest;
}
public void Resume()
{
_pauseFlag.Set();
}
public void Stop()
{
_shutdownFlag.Set();
_pauseFlag.Set();
_thread.Join();
}
private void DoDelegatedMethod()
{
do
{
_action();
}
while (!_shutdownFlag.WaitOne(0));
}
}
where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts
BackgroundWorker is actually ideal for this, as it was designed for this type of scenario. The warning is that you shouldn't change UI elements inside of DoWork, but rather via ReportProgress and the ProgressChanged event.
The reason the warning exists is "DoWork" is executed on a background thread. If you set a UI element value from there, you'll get a cross threading exception. However, ReportProgress/ProgressChanged automatically marshals the call back into the proper SynchronizationContext for you.
Take a look at the Dispatcher object in WPF. You can, and should in your scenario, run the long running tasks on a background thread and the BackgroundWorker is a good way to do it. When you need to update the UI you need to verify access to the UI thread and if you don't have it use the dispatcher to invoke an update method on the UI thread.
There are two possible causes here: first, that the blocking task is blocking the UI thread rather than running on a background thread, and second, that the background thread is starving the UI thread so that it never gets the chance to respond to input. You need to find out which of these is the case. A crude way to do this is, in your Click handler, Debug.WriteLine the current thread ID (Thread.CurrentThread.ManagedThreadId), and do the same in the RunFullAutomation callback.
If these print the same number, then you have the first problem. Reed and TheZenker have provided solutions to this.
If these print different numbers, then you are already on a worker thread, and you have the second problem. (BackgroundWorker may get you to the worker thread more elegantly, and will help with updating the UI, but it won't stop starvation.) In this case the simplest fix is probably to set _thread.Priority = ThreadPriority.BelowNormal; before starting the worker thread.
By the way, your code never appears to actually call AutomationThread.Start, which means the RunFullAutomation callback isn't even executed. Is this just a typo?
I'd advise against rolling out your own Task class given that .NET 4 has full support for running tasks asynchronously in the background using the Task Parallel Library
That said,you can do what Reed suggests and use a BackgroundWorker which is ideal or if you prefer more control over the nature of how the task si executing, you could use the Task class from System.Threading.Tasks and implement something like so:
public partial class MainWindow : Window
{
CancellationTokenSource source = new CancellationTokenSource();
SynchronizationContext context = SynchronizationContext.Current;
Task task;
public MainWindow()
{
InitializeComponent();
}
private void DoWork()
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(500); //simulate long running task
if (source.IsCancellationRequested)
{
context.Send((_) => labelPrg.Content = "Cancelled!!!", null);
break;
}
context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null);
}
}
private void Start_Click(object sender, RoutedEventArgs e)
{
task = Task.Factory.StartNew(DoWork, source.Token);
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
source.Cancel();
}
}
In DoWork() you use the WPF SynchronizationContext and post messages to update the UI wiget you need.
The example has a progress bar and a label control that is updated on each iteration of the for loop.Cancellation is supported using CancellationTokenSource which is checked in each iteration.
Hope this helps.

Categories