Best Practice winforms async/await keywords - c#

Testing a bit TPL.
I use this code and it works:
async void button1_Click(object sender, EventArgs e)
{
MyClass2 mc2 = new MyClass2();
label1.Text = "Start";
List<string> list = await mc2.GetInfosAsync("test123");
label1.Text = "";
list.ForEach(x => label1.Text += x + "\n");
}
class MyClass2
{
public Task<List<string>> GetInfosAsync(string txt)
{
return Task.Factory.StartNew<List<string>>(() => GetInfos(txt));
}
public List<string> GetInfos(string txt)
{
//doing long-listbuilding-operation
}
}
But I'm wondering if this is best practice of using async and await keywords, I feel a bit uncomfortable by "manipulating" the signature of button1_click event.

Using async on windows form event handler is correct and recommended way to do. If you don't use async await, you may see deadlock (unless you are using ConfigureAwait(false)).
Use async await all the way in your code. Don't use Wait() or Result
One important thing, keep all logic in separate method to make unit testable. It will be difficult to test async void. You cannot change method to async Task
// Will result in compilation error
private async Task button1_Click(object sender, EventArgs e)
// Because of event handler signature
public delegate void EventHandler(object sender, EventArgs e);
One good example to handle this is mentioned in msdn article mentioned below
public async void button1_Click(object sender, EventArgs e)
{
await this.ButtonClickHandlerAsync()
}
// Unit test this
public async Task ButtonClickHandlerAsync()
{
MyClass2 mc2 = new MyClass2();
label1.Text = "Start";
List<string> list = await mc2.GetInfosAsync("test123");
label1.Text = "";
list.ForEach(x => label1.Text += x + "\n");
}
More details-
https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
PS: I am assuming your question is about "Is using async on winform button click event handler good practice"? Technically async is not part of method signature (only method name and parameters). In case your question is actually about signature then please ignore this answer.

I implemented this in a solution for login. You can do it like this.
private async void metroButton1_Click(object sender, EventArgs e)
{
string res= await login();
if (res.Equals("true"))
{
this.Hide();
MainMDIParent mdi = new MainMDIParent();
mdi.Show();
btnLogin.Enabled = true;
}
else
{
MessageBox.Show("Invalid User Credentials", "LOgin", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
txtPassword.Text = "";
txtUsername.Text = "";
btnLogin.Enabled = true;
}
}

There is another approach to achieve this.
Parallel.foreach("list",(currentelement) =>
{
enter code here
});
Basically this will work as async.

Related

Run and Wait for async Task to complete in a Windows Form Application

How can I wait for an async task to complete without freezing the whole Application?
This function works but Cout() gets called while the File is still downloading.
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
Cout(DownloadSuccessMsg);
}
when I do this the whole Application freezes:
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
task.Wait();
Cout(DownloadSuccessMsg);
}
How can I wait correctly before running other code depending on the downloaded file?
private static async Task DownloadWebFile(string url, string fullPath)
{
using var client = new DownloadManager(url, fullPath);
client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
{
SetProgressBarValue((int)progressPercentage);
};
await client.StartDownload();
}
You can mark the method as async void. Returning void from an asynchronous method is usually not a great idea, but in the case of an event handler it's usually considered acceptable.
private async void Btn_test_Click(object sender, EventArgs e)
{
await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin");
Cout(DownloadSuccessMsg);
}

How to execute async/await command that can show result

I tried the below code to execute command in Task.Run.
SshClient ssh;
public Form1()
{
InitializeComponent();
//BackGround Login is needed.
ConnectionInfo info = new ConnectionInfo(hostNameOrIpAddr, portNo, userName,
new AuthenticationMethod[] {
new PasswordAuthenticationMethod(userName, passWord)
ssh = new SshClient(info);
ssh.Connect();
cmd = ssh.CreateCommand(commandString);
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(()=>{
SshCommand cmd = ssh.CreateCommand(commandString);
cmd.Execute();
Console.WriteLine(cmd.Result.ToString());
;});
}
But it doesn't work well.
The reason is probably to Dispose the stream immediatly after starting the task.
One of the ways to use async/await is as following:
Note: The async keyword turns a method into an async method, which allows you to use the await keyword in its body.
private async void button1_Click(object sender, EventArgs e) {
var result = await Task.Run(() => RunSshCmd());
Console.WriteLine(result);
}
Let's say running ssh command will take 5 seconds to finish, just example.
private string RunSshCmd() {
Thread.Sleep(5000);
return "Done.";
}
Note: await can only be used inside an async method.

C# Background Worker Append TextBox

first off I'd like to say I'm brand new to C# so I am not too aware with how the background worker is supposed to be implemented. I have a GUI program that basically pings a domain a returns the response to a textbox. I am able to get it to work normally, however, it freezes the code because it is running on the same thread which is why I am trying to implement a background worker.
Here is the basic setup
private void button1_Click(object sender, EventArgs e)
{
url = textBox1.Text;
button1.Enabled = false;
button2.Enabled = true;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
do
{
if (bgWorker.CancellationPending)
break;
Invoke((MethodInvoker)delegate { monitor(); });
} while (true);
}
public void monitor()
{
textBox2.AppendText("Status of: " + url + "\n");
Status(url);
System.Threading.Thread.Sleep(30000);
}
private void Status(string url)
{
// This method does all the ping work and also appends the status to the Text box as it goes through , as OK or down
}
I have not worked with bgworkers before and as you can imagine it's confusing. I've looked at tons of other articles and I can't seem to get it. Sorry if the code looks crazy, I'm trying to learn.
Use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq;) and then you can do this:
private void button1_Click(object sender, EventArgs e)
{
var url = textBox1.Text;
Observable
.Interval(TimeSpan.FromMinutes(0.5))
.SelectMany(_ => Observable.Start(() => Status(url)))
.ObserveOn(this)
.Subscribe(status => textBox2.AppendText("Status of: " + status + "\n"));
}
You then just need to change Status to have this signature: string Status(string url).
That's it. No background worker. No invoking. And Status is nicely run on a background thread.
You've got several mistakes. First,
Invoke((MethodInvoker)delegate
{
monitor();
});
will call monitor() on your UI thread. In almost all cases you should not call methods on other threads. You especially should not call methods that block or do anything that takes more than a few milliseconds on your UI thread, and that is what this does:
System.Threading.Thread.Sleep(30000);
Instead of calling a method on another thread; submit immutable data to the other thread and let the thread decide when to handle it. There is an event already built in to BackgroundWorker which does that. Before you call bgWorker.RunWorkerAsync() do this:
url = new Uri(something);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.ProgressChanged += Bgw_ProgressChanged;
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox2.AppendText("Status of: " + url + ": " + e.UserState.ToString()
+ Environment.NewLine);
}
Your bgWorker_DoWork should look more like this:
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgw.CancellationPending)
{
System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
var status = ResultOfPing(e.Argument as Uri);
bgw.ReportProgress(0, status);
}
e.Cancel = true;
}
and you should call it like this:
bgWorker.RunWorkerAsync(url);
You've got a second problem. BackgroundWorker creates a thread, and your thread is going to spend most of its time blocked on a timer or waiting for network responses. That is a poor use of a thread. You would be better off using completion callbacks or async/await.
The background worker is running on a thread pool thread, but your call to Status and Sleep is running on the UI thread. You need to move that stuff back into bgWorker_DoWork.
Try this code:
public partial class Form1 : Form
{
bool cancel;
public Form1()
{
InitializeComponent();
}
public void StartPinging()
{
this.cancel = false;
startButton.Enabled = false;
stopButton.Enabled = true;
responseBox.Clear();
responseBox.AppendText("Starting to ping server.");
responseBox.AppendText(Environment.NewLine);
var bw = new BackgroundWorker
{
WorkerReportsProgress = false,
WorkerSupportsCancellation = true
};
bw.DoWork += (obj, ev) =>
{
while (!cancel)
{
// Ping Server Here
string response = Server.PingServer();
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText(response);
responseBox.AppendText(Environment.NewLine);
}));
}
};
bw.RunWorkerCompleted += (obj, ev) =>
{
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText("Stopped pinging the server.");
responseBox.AppendText(Environment.NewLine);
startButton.Enabled = true;
stopButton.Enabled = false;
}));
};
bw.RunWorkerAsync();
}
delegate void UiMethod();
private void startButton_Click(object sender, EventArgs e)
{
StartPinging();
}
private void stopButton_Click(object sender, EventArgs e)
{
responseBox.AppendText("Cancelation Pressed.");
responseBox.AppendText(Environment.NewLine);
cancel = true;
}
}
public class Server
{
static Random rng = new Random();
public static string PingServer()
{
int time = 1200 + rng.Next(2400);
Thread.Sleep(time);
return $"{time} ms";
}
}
Erwin, when dealing with C# - threads and UI elements usually you will come across cross-thread operations i.e. Background thread with UI threads. This interaction needs to be done in thread safe way with the help of Invoke to avoid invalid operations.
Please look into below resource: InvokeRequired section.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls

How to call a function after thread finished?

I try to call a function after thread finished but I can't .
I only can use while(threadName.isAlive) method before my function caller code , but it's not good because the program stops when i use this code . have you any idea ?
public partial class Form1 : Form
{
Thread myThread;
string myString = string.Empty;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
myThread = new Thread(write);
myThread.Start();
while (myThread.IsAlive) ;
textBox1.Text = myString;
}
public void write()
{
for (int i = 0; i < 10; i++) {
myString += "aaa " + i + "\r\n";
Thread.Sleep(1000);
}
}
}
If you must attach to a Thread rather than a Task then you can just start a task to wait for the thread to exit and then run some additional code, like this:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static void Main()
{
Thread thread = new Thread(work);
thread.Start();
Task.Run(() =>
{
thread.Join();
Console.WriteLine("Run after thread finished");
});
Console.ReadLine();
}
static void work()
{
Console.WriteLine("Starting work");
Thread.Sleep(1000);
Console.WriteLine("Finished work");
}
}
}
However, the modern way to approach this is to use Task, await and async.
For example:
async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Awaiting task";
await writeAsync();
textBox1.Text = "Task finished";
}
Task writeAsync()
{
return Task.Run(() => write());
}
void write()
{
Thread.Sleep(10000);
}
If you try this second approach, you'll see that the UI remains responsive while the textbox says "Awaiting task".
Also note that normally you'd want to stop the user from being able to press the button again while the task is being awaited, to avoid multiple tasks being run. The easiest way to do that is to disable the button while the task is active like so:
async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
textBox1.Text = "Awaiting task";
await writeAsync();
textBox1.Text = "Task finished";
button1.Enabled = true;
}
Switch to Task from Thread and let .Net do the (low level) work for you:
public async Task<string> write() {
string myString = string.Empty;
for (int i = 0; i < 10; i++) {
myString += "aaa " + i + "\r\n";
await Task.Delay(1000);
}
return myString;
}
private async void button1_Click(object sender, EventArgs e) {
string result = await write();
// continue with (please, notice await) with assigning
textBox1.Text = result;
}

Async method does not return control flow to form

I have a problem with my async method in WPF application. I want to implement asynch await pattern in my WPF application to retain form responsiveness when querying database with EntityFramework. I did everything as described in microsoft examples but it doesn't return control flow to the application as example applications do.
Here is my button click code:
private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
var loggedIn = await _gluUserRepository.LoginAsync(LoginTextBox.Text, PasswordTextBox.Text);
Switcher.Switch(new Loader());
if (loggedIn)
{
UserName = LoginTextBox.Text;
Switcher.Switch(new Blank());
}
else
{
UserName = String.Empty;
MessageBox.Show("Błędny login lub hasło. Spróbuj ponownie.");
Switcher.Switch(new Login());
}
}
Here is my LoginAsync method:
public async Task<bool> LoginAsync(string login, string password)
{
string hashedPassword = PasswordHasher.Hash(password);
var user = await _dbContext.Users.FirstOrDefaultAsync(x => x.UserName == login);
if (user != null && user.Password == hashedPassword)
return true;
return false;
}
I've seen the same usage of async/await in Microsoft example applications, however, their WPF apps return control to Window handle, i.e. I can move window around when in my app that is impossible.
What I want to achieve is using async/await pattern with WPF application to retain responsiveness of application. I want to display loading circle when querying database and return to method when querying has been completed.
Anyone has an idea what am I doing wrong?
If I understand correctly
Switcher.Switch(new Loader());
is what shows the 'loading circle'.
You need to call it before you await the login. As it is now, your handler does everything else after LoginAsync returns.
You may also want to look at ReactiveUI. It provides a useful framework solving your problem.
It's hard to make guesses, but try changing the beginning of your LoginButton_Click code to start the task and await for its result later, like this:
private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
var loggedInTask = _gluUserRepository.LoginAsync(LoginTextBox.Text, PasswordTextBox.Text);
MessageBox.Show("before await");
var loggedIn = await loggedInTask;
MessageBox.Show("after await");
// ...
}
You should be seeing two message boxes, one after another, both of them should be responsive, i.e. moveable and click-able. If you do see such behaviour, then the problem is most likely with your Switcher.Switch and has nothing to do with asynchronous methods of EntityFramework.
I've found solution. Though I don't know why it actually works. But it doesn't block UI thread. I don't think it's thread safe too, you should we weary.
private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
var loginTask = userRepository.LoginAsync(LoginTextBox.Text, PasswordTextBox.Password);
controller.DisplayPageLoader();
DbUser loginResult = await loginTask;
if (loginResult != null)
{
controller.DisplayPageNewMeal();
controller.SetLoggedUser(loginResult);
}
else
{
MessageBox.Show("Błędny login lub hasło. Spróbuj ponownie.");
controller.DisplayPageLogin();
}
}
and then in repository
public Task<DbUser> LoginAsync(string login, string password)
{
return Task.Run<DbUser>( () => Login(login, password));
}
private DbUser Login(string login, string password)
{
try
{
string hashedPassword = PasswordHasher.Hash(password);
var user = _dbContext.Users.FirstOrDefaultAsync(x => x.UserName == login);
if (user.Result != null && user.Result.Password == hashedPassword)
return user.Result;
return null;
}
catch(Exception ex)
{
_logger.Error("Blad logowania uzytkownika", ex);
return null;
}
}
i have main form with async load method :
private async void MainMenu_Load(object sender, EventArgs e)
{
await _connection.Start();
await _myHub.Invoke("Join");
_myHub.On("recieved", data =>
{
var obj = JsonConvert.DeserializeObject(data);
messagewindows = new SendMessage(User, obj.Sender);
if ((obj.Reciever == User ) )
{
messagewindows. txtHistory.Text += obj.Sender + " :" + obj.text + Environment.NewLine;
messagewindows.Show();
}
});
}
i show second form for show message but form hang crash app :
private async void SendMessage_Load(object sender, EventArgs e)
{
Text = Reciever;
await _connection.Start();
await _myHub.Invoke("Join");
_myHub.On("recieved", data =>
{
var obj = JsonConvert.DeserializeObject<MessageSent>(data);
if ((obj.Reciever == Sender || obj.Sender == Sender) && (obj.Sender == Reciever || obj.Reciever == Reciever))
{
txtHistory.Text += obj.Sender + " :" + obj.text + Environment.NewLine;
txtHistory.SelectionStart = txtHistory.Text.Length;
txtHistory.ScrollToCaret();
}
});
}

Categories

Resources