Pergunta

private void WaitForDriveToBecomeReady()
{
    AutoResetEvent syncEvent = new AutoResetEvent(false); //set wait signal to use later

    //dispatcher to be able to change stuff in xaml from within thread
    Action action1 = new Action(delegate() { grdMain.Children.Add(notification); });
    Action action2 = new Action(delegate() { grdMain.Children.Remove(notification); });
    Thread restoreThread1 = new Thread(()=>{
        grdMain.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, action1); //show a notification

        Thread.Sleep(1500); //sleep a bit...

        grdMain.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, action2); //hide a notification

        syncEvent.Set(); //signal to continue at *.WaitOne()
    });
    restoreThread1.Start();

    syncEvent.WaitOne(); //let main thread wait until *.Set(); is called
}

The above code works perfect IF you comment out the two grdMain.Dispatcher.Invoke(...);. It also works perfekt if you comment out the *.Set(); and *.WaitOne(); But WHYYYY? I need both ^^. I don't get it...

Foi útil?

Solução 2

I finally had time to continue read more about async and await. Thanks @Jacob for pointing out what was the problem.

Here is my working async code for a toast notification that appears as long as you don't connect your drive.

//the method
public async Task WaitForDriveAsync(string path, string waitingToastText)
{
    int width = 300;
    int height = 125;
    TextBlock toastTextBlock = new TextBlock() { Text = waitingToastText, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, FontSize = 23, Width = (width - 15), TextWrapping = TextWrapping.WrapWithOverflow };
    Grid notification = new Grid();
    notification.Width = width;
    notification.Height = height;
    notification.Background = Brushes.Red;
    notification.Margin = new System.Windows.Thickness(0, 27, 0.4, 0);
    notification.VerticalAlignment = VerticalAlignment.Top;
    notification.HorizontalAlignment = HorizontalAlignment.Right;
    notification.Children.Add(toastTextBlock);

    grdMain.Children.Add(notification);

    while (!Directory.Exists(path))
    {
        await Task.Delay(1000);
    }

    grdMain.Children.Remove(notification);
}

//to call it
private async void btnBackupNow_Click(object sender, RoutedEventArgs e)
{
    await WaitForDriveAsync(@"B:\", "Please connect your drive.");
}

Outras dicas

Assuming WaitForDriveToBecomeReady is called on the Dispatcher's thread, you are explicitly introducing a deadlock.

Consider the course of execution

  • You set up the reset event
  • The thread starts executing
  • The dispatcher thread calls syncEvent.WaitOne(), that thread is now blocked until that event gets set
  • The second thread executes the Dispatcher.Invoke; this puts a message in the Dispatcher's queue and waits until it processes it (on the main thread)

So you have the main thread blocked waiting for the event that will eventually be set by the second thread. And you have the second thread blocked waiting for the main thread to process a message. Textbook deadlock.

In general, waiting on the UI thread is bad; it's easy to get deadlocks like this, but even if it works, you're still blocking the UI from updating, creating an unresponsive program. Based on the snippet above it's difficult to say how best to reorganize your code so that you don't have to block the UI thread, but it seems like this concept (preparing a drive, and doing something once it's ready) would be a candidate for an asynchronous method.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top