In my WPF application:
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using System.Diagnostics;
namespace CloudKey
{
/// <summary>
/// Interaction logic for Page1.xaml
/// </summary>
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
AuthText.Visibility = Visibility.Hidden;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
AuthText.Visibility = Visibility.Visible;
await Task.Run(() => Authenticate());
Task.Factory.StartNew(() => Authenticate());
Task.Run(() => Authenticate());
Authenticate();
}
void Authenticate()
{
//Do Stuff
}
}
}
No matter which way I try to call Authenticate
with Tasks
it just doesn't run. Am I using Task
wrong?
Using await (and async) causes an exception to be thrown:
System.InvalidOperationException was unhandled Message: An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll Additional information: The calling thread cannot access this object because a different thread owns it.
Using just Task.Run or Task.Factory.StartNew causes the Authenticate method not to be run at all. If I add a breakpoint to the Authenticate method it isn't reached.
Just calling the method with Authenticate() runs the entire method without issue, but it freezes the UI making "AuthText.Visibility = Visibility.Visible;" useless.
To be honest, I really just want the UI to update with the message "Authenticating..." and THEN run everything in the method when I click the button. Is there perhaps an easier way to do that?
THIS IS THE WORKING CODE FOR REFERENCE:
using System;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using System.Diagnostics;
using System.Threading.Tasks;
namespace CloudKey
{
/// <summary>
/// Interaction logic for Page1.xaml
/// </summary>
public partial class Page1 : Page
{
public Page1()
{
InitializeComponent();
//private void PasswordBox_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { button_Click } }
AuthText.Visibility = Visibility.Hidden;
}
private void button_Click(object sender, RoutedEventArgs e) //ON CONTINUE BUTTON CLICK
{
AuthText.Visibility = Visibility.Visible;
Task.Run(() => Authenticate());
}
void Authenticate()
{
Dispatcher.Invoke(
() =>
{
//ALL MY CODE HERE;
});
}
}
}
The issue is that you're not waiting for the asynchronous task to complete, so it appears like "nothing happens" - when in fact something does happen. When you invoke either Task.Run
or Task.Factory.StartNew
you're essentially performaing a fire-and-forget, unless you correctly handle the Task
.
private async void button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() => Authenticate()); // Stuff happens
}
void Authenticate()
{
// DO STUFF
}
In the example above adding the keyword async
to the event handler allows the method to utilize the await
keyword. The await
keyword is where all the magic really occurs...but it will then work as you'd expect, ie; "Stuff happens".
When I do beat Stephen Cleary to these answers I usually point people to his blogs, this one in particular should help clarify this for you.
Note
Writing an async void
is strongly discouraged! The only exception is in your example where you are applying it to an event handler. Finally, when using Task
, and Task<T>
with the async / await
keywords - do so through the entire stack. I would change your Authenticate
method to return a Task
for example, such that it can be awaited. Try invoking Task.Run
at the lowest level possible.
private async void button_Click(object sender, RoutedEventArgs e)
{
await Authenticate(); // Stuff happens
}
Task Authenticate()
{
return _authModule.Authenticate();
}
Update
Based on your comments, do the following:
private void button_Click(object sender, RoutedEventArgs e)
{
bool authenticated = false;
try
{
AuthText = "Authenticating...";
authenticated = Authenticate(); // Stuff happens
}
finally
{
AuthText = authenticated ? "Authenticated" : "Oops!";
}
}
bool Authenticate()
{
// Return if auth was successful
}
When you modify UI stuff in a new thread then you need to use Dispatcher.Invoke
or you can use InvokeAsync
private void Button_Click( object sender, RoutedEventArgs e ) { Task.Run( () => Authenticate() ); }
public void Authenticate()
{
Dispatcher.Invoke(
() =>
{
ClickButton.Content = "Text Changed";
} );
}
By using Dispatcher
You are telling WPF that run this code block on main thread which has access to update your GUI controls.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.