简体   繁体   中英

Need help making WPF application update UI asynchronously

I'm having trouble making my ComputeListener function work as expected.

I want the StatusBlock.Text to be immediately updated on the UI even before Compute() begins, but I cannot get this to work.

I know this question has been asked elsewhere on StackOverflow, but none of the solutions that I saw worked for me.

I've dealt with several problems along the way, but the current issue is that no changes are made until ComputeListener() has fully completed its execution and all other features are locked until then.

I want to also be able to click the close button to stop the application if Compute() is taking too long, but this cannot happen either.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public void ComputeListener(object sender, RoutedEventArgs args)
    {
        var t = Task.Run(() => Dispatcher.BeginInvoke(new Action(() => { StatusBlock.Text = "Status: Processing"; })));
        t.Wait();
        Task.Run(() => Dispatcher.BeginInvoke(new Action(() => { Compute(); })));
    }
    private void Compute()
    {
       //code here
    }

Looks like you've correctly identified that executing Compute() on the UI thread will stop the StatusBlock.Text change from reflecting in the UI as desired, and that Task should be used.

However you get cross-thread exceptions trying to update the UI from a worker thread, so you've used the Window.Dispatcher to get back on the UI thread.

As Icepickle recommends - you want async / await here. I think the following should work:

    public async void ComputeListener(object sender, RoutedEventArgs args)
    {
        StatusBlock.Text = "Status: Processing";
        await Task.Run(() => Compute());
    }

    private void Compute()
    {
       //code here
    }

Or even better:

    public async void ComputeListener(object sender, RoutedEventArgs args)
    {
        StatusBlock.Text = "Status: Processing";
        await ComputeAsync();
    }

    private async Task ComputeAsync()
    {
       //async code here
    }

Note if your implementation of Compute() also needs access to the UI, you'll still run into issues with this - this is a code smell as your business logic (ie computation) should be independent of the WPF front-end.

I'd recommend moving towards an MVVM approach - this Xamarin.Forms documentation explains the general concepts well, which can be applied to WPF in much the same way

You should use the asynchronous dispatcher member Dispatcher.InvokeAsync instead of wrapping it into a Task .

Task.Run is always awaitable to execute it asynchronously. Task.Wait() contradicts the idea as it forces the Task to execute synchronously, since it is blocking the thread until the Task has completed, cancelled or faulted. Read the remarks of Microsoft Docs: Task.Wait . When used in combination with Task.Run chances are high for introducing a deadlock. So always avoid Task.Wait and use the asynchronous and awaitable versions Task.WaitAny and Task.WaitAll if you need to join multiple Task instances.

public partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  }

  public async void ComputeListener(object sender, RoutedEventArgs args)
  {
    // Don't block while waiting for Dispatcher to execute the action
    await Dispatcher.InvokeAsync(() => StatusBlock.Text = "Status: Processing");

    // Executes asynchronously on a background thread (thread pool thread).
    // Use for CPU bound operations
    await ComputeOnNewBackgroundThreadAsync();

    // Executes asynchronously on the current thread.
    // Use for lightweight async operations 
    Task task = new Task(ComputeOnCurrentUiThread);
    task.Start();
    await task;

    // Waiting for tasks example
    var tasks = new List<Task>();
    Task currentThreadTask = new Task(ComputeOnCurrentUiThread);
    currentThreadTask.Start();
    tasks.Add(currentThreadTask);

    Task backgroundThreadTask = ComputeOnNewBackgroundThreadAsync();
    tasks.Add(backgroundThreadTask);

    // Wait asynchronously for both Task instances to complete
    await Task.WhenAll(tasks);
  }

  private async Task ComputeOnNewBackgroundThreadAsync()
  {
    await Task.Run(
      () =>
      {
        //code here
      });
  }

  private void ComputeOnCurrentUiThread()
  {
    //code here
  }
}

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