Cannot use async in closing method - c#

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;
}
}

Related

How do I show a modal dialog that tracks a task?

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.

How to use DerralManager GetDeferral() in WPF Closing Event?

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);
}
}
}

DeadLock on task.Wait() with Task which edit UI

I'm trying to find some solutions to my problem here, but with no result (or I just do not get them right) so if anyone could help / explain i will be really gratefull.
I'm just developing a tool for system administrators using Win Form and now I need to create a continuous ping on the selected machine which is running on the background. There is an indicator for Online status on UI which I need to edit with background ping. So right now I'm in this state:
Class A (Win form):
ClassB activeRelation = new ClassB();
public void UpdateOnline(Relation pingedRelation)
{
//There is many Relations at one time, but form shows Info only for one...
if (activeRelation == pingedRelation)
{
if (p_Online.InvokeRequired)
{
p_Online.Invoke(new Action(() =>
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure
));
}
else
{
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure;
}
}
}
//Button for tunring On/Off the background ping for current machine
private void Btn_PingOnOff_Click(object sender, EventArgs e)
{
Button btn = (sender is Button) ? sender as Button : null;
if (btn != null)
{
if (activeRelation.PingRunning)
{
activeRelation.StopPing();
btn.Image = Properties.Resources.Switch_Off;
}
else
{
activeRelation.StartPing(UpdateOnline);
btn.Image = Properties.Resources.Switch_On;
}
}
}
Class B (class thats represent relation to some machine)
private ClassC pinger;
public void StartPing(Action<Relation> action)
{
pinger = new ClassC(this);
pinger.PingStatusUpdate += action;
pinger.Start();
}
public void StopPing()
{
if (pinger != null)
{
pinger.Stop();
pinger = null;
}
}
Class C (background ping class)
private bool running = false;
private ClassB classb;
private Task ping;
private CancellationTokenSource tokenSource;
public event Action<ClassB> PingStatusUpdate;
public ClassC(ClassB classB)
{
this.classB = classB;
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ping = PingAction(token);
running = true;
}
public void Stop()
{
if (running)
{
tokenSource.Cancel();
ping.Wait(); //And there is a problem -> DeadLock
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
private async Task PingAction(CancellationToken ct)
{
bool previousResult = RemoteTasks.Ping(classB.Name);
PingStatusUpdate?.Invoke(classB);
while (!ct.IsCancellationRequested)
{
await Task.Delay(pingInterval);
bool newResult = RemoteTasks.Ping(classB.Name);
if (newResult != previousResult)
{
previousResult = newResult;
PingStatusUpdate?.Invoke(classB);
}
}
}
So the problem is in deadlock when I cancel token and Wait() for task to complete -> it's still running, but While(...) in task is finished right.
You have a deadlock because ping.Wait(); blocks UI thread.
You should wait for task asynchronously using await.
So, if Stop() is event handler then change it to:
public async void Stop() // async added here
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
If it is not:
public async Task Stop() // async added here, void changed to Task
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
As mentioned by #JohnB async methods should have Async suffix so, the method should be named as StopAsync().
Similar problem and solution are explained here - Do Not Block On Async Code
You should avoid synchronous waiting on tasks, so you should always use await with tasks instead of Wait() or Result. Also, as pointed by #Fildor you should use async-await all the way to avoid such situations.

How to display WatiForm while executing method Async-Await

In my c# winforms application, while displaying the form I am loading the data in LoadDataAsync method, but before loading the data I want to start displaying the Splash Screen, which is not happening can someone guide me what I am doing wrong...or any ideas.
public partial class DepartmentListDetailView : BaseForm
{
private DepartmentDataScope _departmentDataScope;
private AppConfigDataScope _appConfigDataScope;
private DepartmentSearch _departmentSearch;
public DepartmentListDetailView() : base()
{
InitializeComponent();
Init();
}
private async void Init()
{
_departmentDataScope = new DepartmentDataScope();
_appConfigDataScope = new AppConfigDataScope();
_departmentSearch = new DepartmentSearch();
var res = await LoadDataAsync();
}
private async Task<bool> LoadDataAsync()
{
Ssm.ShowWaitForm(); // before loading the data, I want to display the spalsh screen
var result = await _departmentDataScope.FetchDataAsync();
BbiSearch.Enabled = true;
if (Ssm.IsSplashFormVisible)
{
this.Invoke((MethodInvoker) Ssm.CloseWaitForm);
}
return true;
}
}
Thanks
Show it before calling the async method like below code.
Ssm.ShowWaitForm();
var res = await LoadDataAsync();
if (Ssm.IsSplashFormVisible)
{
this.Invoke((MethodInvoker) Ssm.CloseWaitForm);
}

Handling CancellationToken from different class

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?

Categories

Resources