Task continuation to maintain UI thread responsiveness - c#

I'm using Tasks in WinForms to remove expensive methods from my UI thread. In my updateComplete and updateFailed tasks, I have to set _updateMessageTaskInProgress to false and enable my controls. Is there any way I can do this in a separate task which either updateComplete or updateFailed continues too once either is complete (as I currently have duplicate code)? Plus, is there a better way of implementing _updateMessageTaskInProgress - I don't want more than one task to run at the same time.
private void PerformUpdate()
{
if (!_updateMessageTaskInProgress)
{
LoadButton.Enabled = false;
MonthEndDateEdit.Enabled = false;
BankIssuerListEdit.Enabled = false;
Task updateMessages = Task.Factory.StartNew(() =>
{
_updateMessageTaskInProgress = true;
ExpensiveMethod();
});
// Task runs when updateMessages completes without exception. Runs on UI thread.
Task updateComplete = updateMessages.ContinueWith(update =>
{
DoSuccessfulStuff();
_updateMessageTaskInProgress = false;
LoadButton.Enabled = true;
MonthEndDateEdit.Enabled = true;
BankIssuerListEdit.Enabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.NotOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
// Task runs when updateMessages completes with exception. Runs on UI thread.
Task updateFailed = updateMessages.ContinueWith(task =>
{
DoFailureStuff();
_updateMessageTaskInProgress = false;
LoadButton.Enabled = true;
MonthEndDateEdit.Enabled = true;
BankIssuerListEdit.Enabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}

Why don't you just extract a method?
private void SetLock(bool lock)
{
LoadButton.Enabled = !lock;
MonthEndDateEdit.Enabled = !lock;
BankIssuerListEdit.Enabled = !lock;
_updateMessageTaskInProgress = lock;
}
private void PerformUpdate()
{
if (!_updateMessageTaskInProgress)
{
SetLock(true);
Task updateMessages = Task.Factory.StartNew(() =>
{
ExpensiveMethod();
});
// Task runs when updateMessages completes without exception. Runs on UI thread.
Task updateComplete = updateMessages.ContinueWith(update =>
{
DoSuccessfulStuff();
SetLock(false);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.NotOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
// Task runs when updateMessages completes with exception. Runs on UI thread.
Task updateFailed = updateMessages.ContinueWith(task =>
{
DoFailureStuff();
SetLock(false);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}

I would use the Event Based Asynchronous-TYPE Pattern for this. A simplified version of the code I use to spin-off method onto a background thread using TPL is below
private void TaskSpin(TaskScheduler uiScheduler,
Func<TaskScheduler, object[], bool> asyncMethod,
object[] methodParameters)
{
try
{
Task asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Check task status.
switch (task.Status)
{
// Handle any exceptions to prevent UnobservedTaskException.
case TaskStatus.RanToCompletion:
if (asyncTask.Result)
UpdateUI(uiScheduler, "OK");
else
{
string strErrComplete = "Process failed.";
UpdateUI(uiScheduler, strErrComplete);
}
break;
case TaskStatus.Faulted:
string strFatalErr = String.Empty;
UpdateUI(uiScheduler, "Fatal Error);
if (task.Exception != null)
strFatalErr = task.Exception.InnerException.Message;
else
strFatalErr = "Operation failed";
MessageBox.Show(strFatalErr);
break;
}
asyncTask.Dispose();
return;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception eX)
{
Utils.ErrMsg(eX.Message);
}
}
I hope this helps.
Edit. Note, in the above uiScheduler is the TaskScheduler for the UI Thread. That is
TaskSheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Related

Blocking UI thread dosen't render UI components of an other window

In my application I have a scenario where I need to block the UI Thread when I perform an action. While its blocked I have an other window that needs to show a message saying "performing action please wait" when the UI is blocked. Problem is the window shows up but it never displays the message. Please help. Even DoEvents() dosen't help. It just so lightly displays the message
Note:
I don't want the method as async because I want to block the UI when it runs.
I don't want to say ShowDialog() because it just blocks there.
private void ViewModel_PerformPrimeAction(InstrumentAction Action)
{
bool abort = false;
CommandRunningWindow cmdDialog = null;
if (Action == InstrumentAction.Prime)
{
if (Xceed.Wpf.Toolkit.MessageBox.Show((string)TryFindResource("ConfirmPrimeInstrument"),
ApplicationSettingsViewModel.Instance.ProductName, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
this.IsEnabled = false;
//This below line never shows the message.
cmdDialog = ShowCommandWindow(ViewModelsHelper.GetResourceString("PerformingPrime"));
System.Windows.Forms.Application.DoEvents();
}
UIUtils.OverrideCursor = System.Windows.Input.Cursors.Wait;
try
{
// This operation takes 10 seconds
QXInstrumentViewModel.Instance.Prime(() => { if (abort) throw new RunAbortedException(null); });
}
catch (RunAbortedException)
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentAborted");
cmdDialog?.Close();
}
catch (Exception ex)
{
var message = QXInstrumentViewModel.ToErrorCode(ex);
TokenSource = new System.Threading.CancellationToken(true);
if (message != null)
{
errorMessage = string.Format((string)TryFindResource("CompletePrimeInstrumentWithError"), Convert.ToInt32(message), errorMessage);
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentWithUnknownError");
}
cmdDialog?.Close();
}
UIUtils.OverrideCursor = null;
this.IsEnabled = true;
}
public CommandRunningWindow ShowCommandWindow(string message)
{
CommandRunningWindow cmdDialog = new CommandRunningWindow();
cmdDialog.Message = message;
cmdDialog.Owner = WPFUtils.GetActiveWindow();
cmdDialog.Show();
return cmdDialog;
}
The CommandRunningWindow has a dependency property of type string (message) that is bound to a textblock.
You can't both block and show a message on the same thread simultaneously.
What you should do is to execute your long-running operation on a background thread - the easiest way to do this is to start a TPL task - and display the message on the UI thread. You may still disable the window. Just make sure that you don't touch the UI on the background thread where your long-running operation executes.
private void ViewModel_PerformPrimeAction(InstrumentAction Action)
{
bool abort = false;
CommandRunningWindow cmdDialog = null;
if (Action == InstrumentAction.Prime)
{
if (Xceed.Wpf.Toolkit.MessageBox.Show((string)TryFindResource("ConfirmPrimeInstrument"),
ApplicationSettingsViewModel.Instance.ProductName, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
this.IsEnabled = false;
//This below line never shows the message.
cmdDialog = ShowCommandWindow(ViewModelsHelper.GetResourceString("PerformingPrime"));
}
UIUtils.OverrideCursor = System.Windows.Input.Cursors.Wait;
Task.Factory.StartNew(() =>
{
// This operation takes 10 seconds
QXInstrumentViewModel.Instance.Prime(() => { if (abort) throw new RunAbortedException(null); });
})
.ContinueWith(task =>
{
if (task.IsFaulted)
{
if (task.Exception != null && task.Exception.GetBaseException() is RunAbortedException)
{
var message = QXInstrumentViewModel.ToErrorCode(ex);
TokenSource = new System.Threading.CancellationToken(true);
if (message != null)
{
errorMessage = string.Format((string)TryFindResource("CompletePrimeInstrumentWithError"), Convert.ToInt32(message), errorMessage);
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentWithUnknownError");
}
}
else
{
errorMessage = (string)TryFindResource("CompletePrimeInstrumentAborted");
}
}
cmdDialog?.Close();
UIUtils.OverrideCursor = null;
this.IsEnabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

WPF UI not updating and not sure why?

I had a timer in my app that was causing too many thread locks so I decided to replace the timer with a task that runs in a loop. Now, when using the task my UI properties are not updating correctly. I figured it was a thread issue so I move the UI updates outside of the task and used await, but the UI still isn't working. In debug I following the code to the UI and it's on the main thread with the correct data but the UI never updates. The symptom is a frozen UI and a process memory that just keep growing. I"m lost on how to fix this one. Maybe someone can help?
private static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null)
{ StaticPropertyChanged?.Invoke(null, new propertyChangedEventArgs(propertyName));
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(() =>
{
switch (e.PropertyName)
{
case "SiderealTime":
TextLst.Content = _util.HoursToHMS(TelescopeHardware.SiderealTime);
break;
case "RightAscension":
TextRa.Content = _util.HoursToHMS(TelescopeHardware.RightAscension, "h ", "m ", "s", 3);
break;
}));
}
private static async void Main_Mount_Loop()
{
_cts = new CancellationTokenSource();
var ct = _cts.Token;
var keepGoing = true;
while (keepGoing)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
Task.Delay(1000, ct);
MoveAxes();
if (ct.IsCancellationRequested)
{
// Clean up here, then...
keepGoing = false;
}
}, ct);
await task;
UpdateUi();
}
}
Reactive extensions seem to be a more suited library for this usage:
Observable
.Interval(TimeSpan.FromSeconds(1), ThreadPoolScheduler.Instance)
.Do(_ => MoveAxes())
.SubscribeOn(DispatcherScheduler.Current)
.Subscribe(_ => UpdateUi());

Rework EventWaitHandle to asynchronously await signal

I need to change current code to not block current thread when EventWaitHandle.WaitOne is called. Problem is that I am awaiting system-wide event. I did not find any proper replacement yet.
Code:
EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.AutoReset, "Local event", out screenLoadedSignalMutexWasCreated);
StartOtherApp();
if (screenLoadedSignalMutexWasCreated)
{
isOtherAppFullyLoaded = handle.WaitOne(45000, true);
if (isOtherAppFullyLoaded )
{
// do stuff
}
else
{
// do stuff
}
handle.Dispose();
signalingCompleted = true;
}
else
{
isOtherAppFullyLoaded = false;
throw new Exception(" ");
}
I need app to continue and not stop on the line where I call WaitOne, ideally there would be await. How can I implement this ?
You can use AsyncFactory.FromWaitHandle, in my AsyncEx library:
isOtherAppFullyLoaded = await AsyncFactory.FromWaitHandle(handle,
TimeSpan.FromMilliseconds(45000));
The implementation uses ThreadPool.RegisterWaitForSingleObject:
public static Task<bool> FromWaitHandle(WaitHandle handle, TimeSpan timeout)
{
// Handle synchronous cases.
var alreadySignalled = handle.WaitOne(0);
if (alreadySignalled)
return Task.FromResult(true);
if (timeout == TimeSpan.Zero)
return Task.FromResult(false);
// Register all asynchronous cases.
var tcs = new TaskCompletionSource<bool>();
var threadPoolRegistration = ThreadPool.RegisterWaitForSingleObject(handle,
(state, timedOut) => ((TaskCompletionSource<bool>)state).TrySetResult(!timedOut),
tcs, timeout);
tcs.Task.ContinueWith(_ =>
{
threadPoolRegistration.Dispose();
}, TaskScheduler.Default);
return tcs.Task;
}

Monitor Async Task with Timeout and cancellation

I All,
I have to monitor an async task, which has to be cancellable and do not perform more than specific Time To Live.
I already knew about the following code.
CancellationTokenSource l_cts = new CancellationTokenSource(timemillis);
which will doing the cancellation ( as far i monitor the token in my async method).
However, this NOT gave me any information about WHY he has been cancelled, Timeout or user cancellation? furthermore, the timeout event is delayed while i did not catch the cancellation with
Token.ThrowIfCancellationRequested();
In order to solve these issues, I wrote the timeout process as follow.
static async Task TestAsync(int processDelaySeconds, int cancelDelaySeconds, int timeoutDelaySeconds )
{
CancellationTokenSource l_cts = new CancellationTokenSource();
// the process to monitor
Task l_process = new Task((state) =>
{
Console.WriteLine("Process BEGIN");
// dummy loop
for (int l_i = 0; l_i != processDelaySeconds; l_i++)
{
Thread.Sleep(1000);
l_cts.Token.ThrowIfCancellationRequested();
}
Console.WriteLine("Process END");
}, null, l_cts.Token);
// register timeout
RegisteredWaitHandle l_rwh = ThreadPool.RegisterWaitForSingleObject(l_cts.Token.WaitHandle,
(state, timedOut) =>
{
if (timedOut)
{
l_cts.Cancel();
Console.WriteLine("Timed out");
}
else
{
Console.WriteLine("Cancel Signaled");
}
},
null, (int)TimeSpan.FromSeconds(timeoutDelaySeconds).TotalMilliseconds, true);
// cancel task
if (cancelDelaySeconds > 0)
{
Task l_cancel = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(cancelDelaySeconds));
l_cts.Cancel();
});
l_cancel.Start();
}
try
{
l_process.Start();
await l_process;
}
catch (OperationCanceledException)
{
Console.WriteLine("Task Cancelled");
}
finally
{
// be sure to unregister the wait handle to cancel the timeout
if (l_process.Status != TaskStatus.Canceled) l_rwh.Unregister(l_cts.Token.WaitHandle);
}
Console.WriteLine("Task Status is : {0}", l_process.Status);
}
static async void Tests()
{
Console.WriteLine("NORMAL PROCESS");
Console.WriteLine("--------------");
await TestAsync(2, 10, 10);
Console.WriteLine();
Console.WriteLine("CANCEL");
Console.WriteLine("------");
await TestAsync(5, 2, 10);
Console.WriteLine();
Console.WriteLine("TIMEOUT");
Console.WriteLine("-------");
await TestAsync(10, 15, 2);
}
Then My question is :
Is there any drawbacks or traps behind the scene ?
A better and more efficient way ??
ps- Goal is performance, not shorter code.
If you need to distinguish between the user's and time-out cancellation, you can use CreateLinkedTokenSource:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp
{
internal class Program
{
// worker
private static void DoWork(CancellationToken token)
{
for (int i = 0; i < 1000; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100); // do the work item
}
token.ThrowIfCancellationRequested();
}
// test
private static void Main()
{
var userCt = new CancellationTokenSource();
var combinedCt = CancellationTokenSource.CreateLinkedTokenSource(
userCt.Token);
combinedCt.CancelAfter(3000); // cancel in 3 seconds
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
userCt.Cancel();
};
var task = Task.Run(
() => DoWork(combinedCt.Token),
combinedCt.Token);
try
{
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.InnerException.Message);
if (task.IsCanceled)
{
if (userCt.Token.IsCancellationRequested)
Console.WriteLine("Cancelled by user");
else if (combinedCt.Token.IsCancellationRequested)
Console.WriteLine("Cancelled by time-out");
else
Console.WriteLine("Cancelled by neither user nor time-out");
}
}
}
}
}
As to your original code, you really did not need ThreadPool.RegisterWaitForSingleObject(l_cts.Token.WaitHandle, ...), there's CancellationToken.Register for that, which returns an IDisposable ready for use with using.
In order to know if your Task has been cancelled or timed out, you can use the Task.WaitAny overload which takes a TimeSpan:
// Index will return -1 if timeout has occured, otherwise will print the index of the completed task
var cnclToken = new CancellationTokenSource().Token
var yourTask = Task.Run(() => { /* Do stuff */ }, cnclToken);
var index = Task.WhenAny(new[] { yourTask }, TimeSpan.FromSeconds(1));
http://msdn.microsoft.com/en-us/library/dd235645(v=vs.110).aspx

Task doesn't seem to start

Why doesn't this code start the task? I am always getting waiting for activation.
Here I'm doing a simple task which tries to read from clipboard and returns me when the clipboard is accessible. When I run the line, I can see that the task status is waiting for completion.Why is this happening?
TaskScheduler scheduler = TaskScheduler.Default;
var fetchTask =
new Task<Boolean>(() =>
{
while (1 == 1)
{
try
{
IDataObject obj = Clipboard.GetDataObject();
return true;
}
catch (Exception e)
{
}
}
})
.ContinueWith<Boolean>(
x =>
{
Console.WriteLine("Task completed");
return true;
},
CancellationToken.None,
TaskContinuationOptions.None, scheduler);
Because... You didn't start it?
fetchTask.Start();
(CW to respect the two existing comments to this effect)
the tasak is never being started.
fetchTask.Start();
I know this is old, but try using TaskFactory if fetchTask.Start() doesn't work
TaskScheduler scheduler = TaskScheduler.Default;
var fetchTask = Task<Boolean>.Factory.StartNew(() =>
{
while (1 == 1)
{
try
{
IDataObject obj = Clipboard.GetDataObject();
return true;
}
catch (Exception e)
{
}
}
})
.ContinueWith<Boolean>(
x =>
{
Console.WriteLine("Task completed");
return true;
},
CancellationToken.None,
TaskContinuationOptions.None, scheduler);

Categories