9

I have created a simple WPF Application and added a button to the default window. When I click on the button, a simulated long working method(simulated using a Thread.Sleep(15000) is called. I am trying to make the button execute asynchronously however despite following online examples, the button and entire window locks as soon as I click and remains so until the Thread.Sleep(...) finishes.

Any ideas why this is happening?

Here is the code:

private void button1_Click(object sender, RoutedEventArgs e)
{
   DoSomeAsyncWork();
}

private void DoSomeAsyncWork()
{
     System.Windows.Threading.Dispatcher.Run();
     Thread thread = new System.Threading.Thread(
         new System.Threading.ThreadStart(
          delegate()
          {
               Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => Thread.Sleep(15000)));
          }
        ));
     thread.Start();
}
H.B.
  • 166,899
  • 29
  • 327
  • 400
fin
  • 1,275
  • 4
  • 18
  • 34

3 Answers3

17

You are putting the long operation back into the UI thread. Let me comment your example:

Thread thread = new System.Threading.Thread( 
    new System.Threading.ThreadStart( 
        delegate() { 
            // here we are in the background thread

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                new Action(() => {
                    // here we are back in the UI thread
                    Thread.Sleep(15000);
                })); 
      } 
    )); 

So, you should modify your example like this:

Thread thread = new System.Threading.Thread( 
    new System.Threading.ThreadStart( 
        delegate() { 
            // here we are in the background thread

            Thread.Sleep(15000);  // <-- do the long operation here

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                new Action(() => {
                    // here we are back in the UI thread

                    // do stuff here that needs to update the UI after the operation finished
                })); 
      } 
    )); 

As others have mentioned, it's easier to use the BackgroundWorker class. Here's an example:

private void DoSomeAsyncWork()   
{   
    BackgroundWorker bw = new BackgroundWorker();

    bw.DoWork += (sender, args) => {
        // do your lengthy stuff here -- this will happen in a separate thread
        Thread.Sleep(15000);
    }

    bw.RunWorkerCompleted += (sender, args) => {
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here

        // do any UI stuff after the long operation here
        ...
    }

    bw.RunWorkerAsync(); // start the background worker
}
Heinzi
  • 167,459
  • 57
  • 363
  • 519
4

By using BeginInvoke you are actually executing the code on the UI thread.

You only need to use this when you are updating your UI from the background worker thread. If you simply have:

 Thread thread = new System.Threading.Thread(
     new System.Threading.ThreadStart(
      delegate()
      {
           Thread.Sleep(15000);
      }
    ));

I think it will work.

However, you aren't raising a "Work Completed" event so you have no way of knowing when (or indeed if) the thread has finished. Look into the BackgroundWorker class. This does a lot of the heavy lifting for you. You just need to plug in your code to the DoWork method.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
  • I am actually trying to execute some code that requires it's own thread and when I place this line of code directly in the first delegate(i.e. where you have the Thread.Sleep(15000)) I get a message saying that the thread can't access objects . This is the message: "The calling thread cannot access this object because a different thread owns it." Apparently, it's a common issue but how I am struggling to get around this without going down the route of using a background worker. So when I added the second nested BeginInvoke call the object is found but the UI is blocked. – fin Sep 23 '11 at 13:43
  • So I guess, what I am looking for is a method to put my logic that seemingly requires access to the UI thread on a new background thread. To summarise, grant access to UI objects but run on a separate thread in the background? – fin Sep 23 '11 at 13:46
  • @Finbar: You can't "grant access". UI operations *must* be done in the UI thread. What you can do, however, is to split the UI operations into small bits, put the big loop in the background thread and call `Dispatcher.BeginInvoke(...short UI operation...)` inside the loop. This causes the UI to be blocked shortly many times instead of once a long time. – Heinzi Sep 25 '11 at 16:14
0

You should use BackgroundWorker.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
Learner
  • 1,490
  • 2
  • 22
  • 35