简体   繁体   中英

Asynchronously Wait for a thread to finish

Well i have a function that takes a screenshot from the pc but unfortunately it blocks the main UI so i decided to make an asynchronous [Threaded call] of it ;However, i find trouble waiting for the result of the Thread before returning the Bitmap.

Here is my code :

/// <summary>
/// Asynchronously uses the snapshot method to get a shot from the screen.
/// </summary>
/// <returns> A snapshot from the screen.</returns>
private Bitmap SnapshotAsync()
{
    Bitmap image = null;
    new Thread(() => image = Snapshot()).Start();

    while (image == null)
    {
        new Thread(() => Thread.Sleep(500)).Start(); //Here i create new thread to wait but i don't think this is a good way at all.
    }
    return image;
}

/// <summary>
/// Takes a screen shots from the computer.
/// </summary>
/// <returns> A snapshot from the screen.</returns>
private Bitmap Snapshot()
{
    var sx = Screen.PrimaryScreen.Bounds.Width;
    var sy = Screen.PrimaryScreen.Bounds.Height;
    var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
    var gfx = Graphics.FromImage(shot);
    gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
    return shot;
}

Although the method above is working asynchronously as i want, I am sure it can be improved. Specially the way i am executing hundreds of threads to wait for the result, A way that i am sure is not good.

So i really need for any one who take a look at the code and tell me how to improve it.

[NOTE I AM USING .NET 3.5]

And thanks in advance.

Problem solved by the help of Eve and SiLo here is the best 2 answers

  • 1 :
>     private void TakeScreenshot_Click(object sender, EventArgs e)
>     {
>       TakeScreenshotAsync(OnScreenshotTaken);
>     }
>     
>     private static void OnScreenshotTaken(Bitmap screenshot)
>     {
>       using (screenshot)
>         screenshot.Save("screenshot.png", ImageFormat.Png);
>     }
>     
>     private static void TakeScreenshotAsync(Action<Bitmap> callback)
>     {
>       var screenRect = Screen.PrimaryScreen.Bounds;
>       TakeScreenshotAsync(screenRect, callback);
>     }
>     
>     private static void TakeScreenshotAsync(Rectangle bounds, Action<Bitmap> callback)
>     {
>       var screenshot = new Bitmap(bounds.Width, bounds.Height,
>                                   PixelFormat.Format32bppArgb);
>     
>       ThreadPool.QueueUserWorkItem((state) =>
>       {
>         using (var g = Graphics.FromImage(screenshot))
>           g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
>     
>         if (callback != null)
>           callback(screenshot);
>       });
>     }
  • 2 :
>     void SnapshotAsync(Action<Bitmap> callback)
>     {
>         new Thread(Snapshot) {IsBackground = true}.Start(callback);
>     }

>     void Snapshot(object callback)
>     {
>         var action = callback as Action<Bitmap>;
>         var sx = Screen.PrimaryScreen.Bounds.Width;
>         var sy = Screen.PrimaryScreen.Bounds.Height;
>         var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
>         var gfx = Graphics.FromImage(shot);
>         gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
>         action(shot);
>     }

Usage, for example, through a button's click:

 void button1_Click(object sender, EventArgs e) { SnapshotAsync(bitmap => MessageBox.Show("Copy successful!")); } 

The async / await keywords do exactly what you are trying to, very elegantly.

Here is how I would convert your methods to the proper pattern:

private static async Task<Bitmap> TakeScreenshotAsync()
{
  var screenRect = Screen.PrimaryScreen.Bounds;
  return await TakeScreenshotAsync(screenRect);
}

private static async Task<Bitmap> TakeScreenshotAsync(Rectangle bounds)
{
  var screenShot = new Bitmap(bounds.Width, bounds.Height, 
                              PixelFormat.Format32bppArgb);

  // This executes on a ThreadPool thread asynchronously!
  await Task.Run(() =>
  {
    using (var g = Graphics.FromImage(screenShot))
      g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);

  });

  return screenShot;
}

Then you would do something like this:

private async void TakeScreenshot_Click(object sender, EventArgs e)
{
  var button = sender as Button;
  if(button == null) return;

  button.Enabled = false;
  button.Text = "Screenshoting...";

  var bitmap = await TakeScreenshotAsync();
  bitmap.Save("screenshot.png", ImageFormat.Png);

  button.Text = "Take Screenshot";
  button.Enabled = true;
}

You can use Event-base Asynchronous Pattern for this:

void SnapshotAsync(Action<Bitmap> callback)
{
    new Thread(Snapshot) {IsBackground = true}.Start(callback);
}

void Snapshot(object callback)
{
    var action = callback as Action<Bitmap>;
    var sx = Screen.PrimaryScreen.Bounds.Width;
    var sy = Screen.PrimaryScreen.Bounds.Height;
    var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
    var gfx = Graphics.FromImage(shot);
    gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
    action(shot);
}

Usage, for example, through a button's click:

void button1_Click(object sender, EventArgs e)
{
    SnapshotAsync(bitmap => MessageBox.Show("Copy successful!"));
}

As the author requested, it doesn't block the original thread. Be careful if you have to operate on the UI through the callback though, remember to use Invoke and its equivalents.

EDIT: Read SiLo's comment for a couple of good practices and optimizations that you can apply to the above code.

I just saw your edit about using 3.5 instead of 4.5. That's too bad, but it is definitely still possible. I've created this second answer so the people using async / await can use the first one as an example.

Now for your solution, it's not too much different really:

private void TakeScreenshot_Click(object sender, EventArgs e)
{
  TakeScreenshotAsync(OnScreenshotTaken);
}

private static void OnScreenshotTaken(Bitmap screenshot)
{
  using (screenshot)
    screenshot.Save("screenshot.png", ImageFormat.Png);
}

private static void TakeScreenshotAsync(Action<Bitmap> callback)
{
  var screenRect = Screen.PrimaryScreen.Bounds;
  TakeScreenshotAsync(screenRect, callback);
}

private static void TakeScreenshotAsync(Rectangle bounds, Action<Bitmap> callback)
{
  var screenshot = new Bitmap(bounds.Width, bounds.Height,
                              PixelFormat.Format32bppArgb);

  ThreadPool.QueueUserWorkItem((state) =>
  {
    using (var g = Graphics.FromImage(screenshot))
      g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);

    if (callback != null)
      callback(screenshot);
  });
}

Sorry, but that is logically not too smart what you try here.

  • You want to take a screenshot.
  • You dont want the UI thread to block, so you go async.

Gratulations. So far that makes sense.

Now comes the part you dont want to tell anyone you tried:

  • Now you want to wait in the UI thread for the async operation to finish.

And back we are to the start - you block the UI thread. Nothing is achieved. You basically logically end up at exactly the same place you started with.

Ok, solution:

  • First, get rid of the thread, use a Task. More efficient.
  • Second, realize that waiting makes no sense in the UI thread. Deactivate the UI elements, then turn them back on at the end of the processing.

Handle this as a state machine issue (UI is in "working" or in "waiting for commands" state), so you do not block. This is the ONLY way to handle that - because at the end the whole async orpeation is useless if you THEN wait for the execution to finish.

You can not start a method, then wait for the processing to finish blocking the thread - if you ever try that, then the whole async operation is a useless proposition.

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