简体   繁体   中英

Task.Run(() => Method()); doesn't run the method?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM