Wpf
I am attempting to delay window closing until all tasks are completed using the
async/await library of StephenCleary https://github.com/StephenCleary/AsyncEx.
The event handler delegate and event arguments definitions:
public delegate void CancelEventHandlerAsync(object sender, CancelEventArgsAsync e);
public class CancelEventArgsAsync : CancelEventArgs
{
private readonly DeferralManager _deferrals = new DeferralManager();
public IDisposable GetDeferral()
{
return this._deferrals.GetDeferral();
}
public Task WaitForDefferalsAsync()
{
return this._deferrals.SignalAndWaitAsync();
}
}
Then in the code behind of the NewWindowDialog.xaml, I override the OnClosing event:
public NewWindowDialog()
{
InitializeComponent();
}
protected override async void OnClosing(System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
base.OnClosing(e);
await LaunchAsync();
}
private async Task LaunchAsync()
{
var vm =(NewProgressNoteViewModel)DataContext;
var cancelEventArgs = new CancelEventArgsAsync();
using (var deferral = cancelEventArgs.GetDeferral())
{
// a very long procedure!
await vm.WritingLayer.CompletionAsync();
}
}
Clearly, this fails since e.Cancel = true is executed before the await. So what am I missing to correctly use GetDeferral() to delay the window closing while the tasks are being completed (in WPF).
TIA
Edit: With the help of everybody, I am currently using this. However, does anybody have a good example of the Deferral pattern on window closing?
Thanks to all.
private bool _handleClose = true;
protected override async void OnClosing(System.ComponentModel.CancelEventArgs e)
{
using (new BusyCursor())
{
if (_handleClose)
{
_handleClose = false;
IsEnabled = false;
e.Cancel = true;
var vm = (NewProgressNoteViewModel)DataContext;
await vm.WritingLayer.SaveAsync();
e.Cancel = false;
base.OnClosing(e);
}
}
}
You don't need a deferral. Just set the CancelEventArgs.Cancel property to true, await the long-running operation and then close. You could use a flag to avoid doing the same thing more than once:
private bool _handleClose = true;
protected override async void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (_handleClose)
{
e.Cancel = true;
await Task.Delay(5000);// a very long procedure!
_handleClose = false;
Close();
}
}
I believe it's a more user-friendly approach to show a modal "Please wait..." message inside your Window.Closing event handler that goes away when the Task is complete. This way, the control flow doesn't leave your Closing handler until it's safe to close the app.
Below is a complete WPF example of how it can be done. Error handling is skipped for brevity. Here's also a related question dealing with a similar problem.
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp
{
public partial class MainWindow : Window
{
Task _longRunningTask;
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (_longRunningTask?.IsCompleted != false)
{
return;
}
var canClose = false;
var dialog = new Window
{
Owner = this,
Width = 320,
Height = 200,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Content = new TextBox {
Text = "Please wait... ",
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center
},
WindowStyle = WindowStyle.None
};
dialog.Closing += (_, args) =>
{
args.Cancel = !canClose;
};
dialog.Loaded += async (_, args) =>
{
await WaitForDefferalsAsync();
canClose = true;
dialog.Close();
};
dialog.ShowDialog();
}
Task WaitForDefferalsAsync()
{
return _longRunningTask;
}
public MainWindow()
{
InitializeComponent();
this.Closing += MainWindow_Closing;
_longRunningTask = Task.Delay(5000);
}
}
}
Related
I have a classic WinForms app which communicates async with the server api. That is realized by async await pattern.
Now I have a problem with the 'Validating'-Event. We use this event for client-side Validation (check if input is null) and set cancel.
If input is valid then I send the input to server async and await the result.
There is my problem. Now the control means that it is valid. When the callback set the cancel event to false it is too late.
Here ist my code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; }
private async void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = await SaveOnServerAsync(CustomerName);
// Here is the Problem: Setting e.Cancel in callback is too late.
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
I tried classic programming (synchron) for this case by 'Wait' the taks in main thread. But then I generated a deadlock.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
// Deadlock because MainThread waits for callback.
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000); // Send given customerName to server in real app.
return customerName.Contains("%") ? false : true;
}
}
Has anyone an idea for a good pattern?
If I set Cancel to false before await the async task then my problem would be that the user click is discarded and the user experience is sufficient.
I found a working way. Except the first Task all other tasks need "ConfigureAwait(false)".
See method 'SaveOnServerAsync'.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string CustomerName { get; set; } = "3";
private void Button1_Validating(object sender, CancelEventArgs e)
{
if (CustomerName == null)
{
e.Cancel = true;
}
else
{
var isValidAndSaved = SaveOnServerAsync(CustomerName).Result;
if (isValidAndSaved)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
}
}
/// <returns>True if the given model was valid and save operation was successfull.</returns>
public async Task<bool> SaveOnServerAsync(string customerName)
{
await Task.Delay(3000)
// The solution is that all deeper Tasks need line below.
.ConfigureAwait(false);
;
return customerName.Contains("%") ? false : true;
}
}
I found many articles about that problem. It sounds a bit of missconcept from MS.
Figure 3 of this MS article explains the deadlock situation:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
The solution to avoid deadlocks is explained here:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#configure-context
It's clear to me if I have a button that triggers an event, but in the case below, I want to pop up a dialog. The code below is a mess, I don't know how to do this right. I think async/await is part of this, but I'm not clear on this case.
class TaskObject : Form
{
public void MyFunc()
{
MyDialog d = new MyDialog(this);
d.ShowDialog(); // I don't want any other interaction except this dialog's controls
}
internal async Task<bool> LongFunction()
{
// ...
return true;
}
}
class MyDialog : Form
{
Task<bool> task;
public async MyDialog(TaskObject o)
{
task = new Task<bool>(o.LongFunction);
await task;
}
void when_LongFunction_does_something_interesting()
{
this.MyTextBox.Text = "Something interesting";
}
void when_task_completes()
{
this.CancelButton.Visible = false;
this.CloseButton.Visible = true;
}
}
There are two points here:
The constructor of your form cannot have the async modifier. As an alternative, you can use the Load event instead.
(Optional) You don't need to pass an instance of the "parent" form to the constructor, you can get it directly from the Owner property if you use ShowDialog(this) instead of ShowDialog().
Also, remember to dispose of any dialog form after you're done with it. Preferably, wrap the usage of it within a using block.
Here's how I would do it; In the TaskObject form:
internal async Task<bool> LongFunction()
{
// Do some magic.
// await ...
return true;
}
public void MyFunc()
{
using (MyDialog d = new MyDialog())
{
d.ShowDialog(this);
}
}
In the MyDialog form:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
await owner.LongFunction();
when_task_completes();
}
If you also want to track the progress of LongFunction, you can add a Progress<T> parameter to it and use it like this:
internal async Task<bool> LongFunction(IProgress<string> progress)
{
// Do some magic.
progress.Report("Something interesting");
// await ...
// More magic.
return true;
}
Then you can do something like this:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
var progress = new Progress<string>(s => when_LongFunction_does_something_interesting(s));
await owner.LongFunction(progress);
when_task_completes();
}
void when_LongFunction_does_something_interesting(string message)
{
this.MyTextBox.Text = message;
}
Note that I used Progress<string> as an example. Instead of string, you can use whatever type works best for your situation.
I make a method called Instance that allow me to have a single instance of the Settings window, like this:
public static async Task<Settings> Instance()
{
if (AppWindow == null)
{
AppWindow = new Settings();
AppWindow.Closing += async (x, y) =>
{
bool close = await AppWindow.CheckSettings();
y.cancel = (close) ? true : false;
AppWindow = null;
};
}
return AppWindow;
}
the CheckSettings have this structure:
private async Task<bool> CheckSettings()
{
//just as example
return true;
}
the method Instance() tell me that there is no await operator inside. Why happen this?
I need to ask also other questions:
Can this logic used inside a property instead of Instance method? How?
Is possible close the window without implement a Task<bool>
UPDATE
based on the helpful answer and comments on this great community I have edited the method as this (now is a property):
public static Settings Instance
{
get
{
if (AppWindow == null)
{
AppWindow = new Settings();
AppWindow.Closing += async (x, y) =>
{
bool close = await AppWindow.CheckSettings();
y.Cancel = close;
//AppWindow.Close();
//AppWindow = null;
};
}
return AppWindow;
}
}
the problem is that the Cancel does not await the CheckSettings()
Set the Cancel property to true before you call your async method:
public static Settings Instance
{
get
{
if (AppWindow == null)
{
AppWindow = new Settings();
//attach the event handler
AppWindow.Closing += AppWindow_Closing;
}
return AppWindow;
}
}
private static async void AppWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
//call the async method
bool close = await AppWindow.CheckSettings();
if (close)
{
AppWindow win = (AppWindow)sender;
//detach the event handler
AppWindow.Closing -= AppWindow_Closing;
//...and close the window immediately
win.Close();
AppWindow = null;
}
}
i want to just stop my backgroundworker when i press a button :
Code looking like :
Button :
private void button6_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
if (isOn == true)
{
isOn = false;
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
this.button6.ForeColor = System.Drawing.Color.Lime;
}
}
else
{
isOn = true;
this.button6.ForeColor = System.Drawing.Color.Red;
backgroundWorker1.CancelAsync();
//////backgroundWorker1.Dispose();
}
And my Backgroundworker_DoWork look like :
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
return;
}
while (true)
{
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
break;
}
backgroundWorker1.Dispose();
click_na_default(hwnd1);
click_F8(hwnd1);
click_na_YELLS(hwnd1);
click_ENTER(hwnd1);
Thread.Sleep(100);
click_na_trade(hwnd1);
Thread.Sleep(100);
click_F8(hwnd1);
click_ENTER(hwnd1);
Thread.Sleep(100);
click_na_default(hwnd1);
Thread.Sleep(4000);
}
if (((BackgroundWorker)sender).CancellationPending)
{
e.Cancel = true;
//set this code at the end of file processing
return;
}
}
And the problem is : I can't .CancelAsync(); just immediately after button press again . My code just DoWork untill just Thread.Sleep(4000); is over.
When i press my button to stop work this gonna stop just after end while loop.
I know i can add
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
return;
}
After everyline in my Backgroundworker_DoWork but it's so stupid and when i get Thread.Sleep(10000); it gonna takes 10 sec...
Is any way to just kill instantly my background worker?
Thanks for help!
I think that standard BackgroundWorker is not suitable for your case and you should do something custom that better support combination of sleep and cancellation. Following code is an idea of what you might want to do:
CancellableBackgroundWorker.cs
This is a class similar to standard BackgroundWorker but providing some callbacks for your goal (see ICancellationProvider and FinishedEvent).
public delegate void CancellableBackgroundJob(ICancellationProvider cancellation);
public interface ICancellationProvider
{
bool CheckForCancel();
void CheckForCancelAndBreak();
void SleepWithCancel(int millis);
}
public class CancellableBackgroundWorker : Component, ICancellationProvider
{
private readonly ManualResetEvent _canceledEvent = new ManualResetEvent(false);
private readonly CancellableBackgroundJob _backgroundJob;
private volatile Thread _thread;
private volatile bool _disposed;
public EventHandler FinishedEvent;
public CancellableBackgroundWorker(CancellableBackgroundJob backgroundJob)
{
_backgroundJob = backgroundJob;
}
protected override void Dispose(bool disposing)
{
Cancel();
_disposed = true;
}
private void AssertNotDisposed()
{
if (_disposed)
throw new InvalidOperationException("Worker is already disposed");
}
public bool IsBusy
{
get { return (_thread != null); }
}
public void Start()
{
AssertNotDisposed();
if (_thread != null)
throw new InvalidOperationException("Worker is already started");
_thread = new Thread(DoWorkWrapper);
_thread.Start();
}
public void Cancel()
{
AssertNotDisposed();
_canceledEvent.Set();
}
private void DoWorkWrapper()
{
_canceledEvent.Reset();
try
{
_backgroundJob(this);
Debug.WriteLine("Worker thread completed successfully");
}
catch (ThreadAbortException ex)
{
Debug.WriteLine("Worker thread was aborted");
Thread.ResetAbort();
}
finally
{
_canceledEvent.Reset();
_thread = null;
EventHandler finished = FinishedEvent;
if (finished != null)
finished(this, EventArgs.Empty);
}
}
#region ICancellationProvider
// use explicit implementation of the interface to separate interfaces
// I'm too lazy to create additional class
bool ICancellationProvider.CheckForCancel()
{
return _canceledEvent.WaitOne(0);
}
void ICancellationProvider.CheckForCancelAndBreak()
{
if (((ICancellationProvider)this).CheckForCancel())
{
Debug.WriteLine("Cancel event is set, aborting the worker thread");
_thread.Abort();
}
}
void ICancellationProvider.SleepWithCancel(int millis)
{
if (_canceledEvent.WaitOne(millis))
{
Debug.WriteLine("Sleep aborted by cancel event, aborting the worker thread");
_thread.Abort();
}
}
#endregion
}
The main trick is to use ManualResetEvent.WaitOne instead of Thread.Sleep for sleeping. With such approach working thread might be safely woken up (for cancellation) from a different (UI) thread. Another trick is to use ThreadAbortException via Thread.Abort to enforce quick end of the background thread execution (and don't forget about Thread.ResetAbort at the end of stack unwinding).
You may use this class as following:
public partial class Form1 : Form
{
private readonly CancellableBackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new CancellableBackgroundWorker(DoBackgroundJob);
_backgroundWorker.FinishedEvent += (s, e) => UpdateButton();
// ensure this.components is created either by InitializeComponent or by us explicitly
// so we can add _backgroundWorker to it for disposal
if (this.components == null)
this.components = new System.ComponentModel.Container();
components.Add(_backgroundWorker);
}
private void UpdateButton()
{
// Ensure we interact with UI on the main thread
if (InvokeRequired)
{
Invoke((Action)UpdateButton);
return;
}
button1.Text = _backgroundWorker.IsBusy ? "Cancel" : "Start";
}
private void button1_Click(object sender, EventArgs e)
{
if (_backgroundWorker.IsBusy)
{
_backgroundWorker.Cancel();
}
else
{
_backgroundWorker.Start();
}
UpdateButton();
}
private void DoBackgroundJob(ICancellationProvider cancellation)
{
Debug.WriteLine("Do something");
// if canceled, stop immediately
cancellation.CheckForCancelAndBreak();
Debug.WriteLine("Do something more");
if (cancellation.CheckForCancel())
{
// you noticed cancellation but still need to finish something
Debug.WriteLine("Do some necessary clean up");
return;
}
// Sleep but cancel will stop and break
cancellation.SleepWithCancel(10000);
Debug.WriteLine("Last bit of work");
}
}
I have a class like so:
public class FtpTaskVideo : IFtpTask
{
//some fields
public CancellationTokenSource tokenSource = new CancellationTokenSource();
private Panel CreatePanel(string text, int count, int value)
{
Panel pnlOutput = new Panel();
pnlOutput.Name = "pnlInfo";
pnlOutput.AutoSize = true;
pnlOutput.BorderStyle = BorderStyle.FixedSingle;
//adding some controls
Button btnUserCancel = new Button();
btnUserCancel.Name = "btnUserCancel";
btnUserCancel.AutoSize = true;
btnUserCancel.Text = "Stop";
btnUserCancel.Click += new EventHandler(btnUserCancel_Click);
pnlOutput.Controls.Add(btnUserCancel);
btnUserCancel.BringToFront();
return pnlOutput;
}
public void btnUserCancel_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
}
public void Start()
{
//some code
while(somethingToDownload)
{
var task = Task<SharedConstants.downloadFtpFileStatus>.Factory.StartNew(() => dff.Download(tokenSource.Token), tokenSource.Token);
try
{
downloadStatus = task.Result;
}
catch (System.AggregateException exc)
{
//do something
}
//some code
}
}
And in the second class (dff):
public Shared.Classes.SharedConstants.downloadFtpFileStatus Download(CancellationToken token)
{
if (token.IsCancellationRequested)
{
return Shared.Classes.SharedConstants.downloadFtpFileStatus.CANCELLED;
}
else //do some stuff
}
Now, I have another class, which dff is an instance of and Download is it's method. One of the things dff does is update and redraw the panel according to the data it gets during Download method operation. How, after it draws a button and I press it can I send the cancel token back to original class to stop it from downloading?