4

好吧,我有一个从 pc 截取屏幕截图的函数,但不幸的是它阻塞了主 UI,所以我决定对其进行异步 [线程调用];但是,在返回位图之前,我发现等待线程结果的麻烦。

这是我的代码:

/// <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;
}

尽管上面的方法可以按我的意愿异步工作,但我相信它可以改进。特别是我执行数百个线程以等待结果的方式,我确信这种方式不好。

所以我真的需要任何人看看代码并告诉我如何改进它。

[注意我使用的是 .NET 3.5]

并提前感谢。

在 Eve 和 SiLo 的帮助下解决的问题是最好的 2 个答案

  • 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);
>     }

用法,例如,通过一个按钮的点击:

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

4 回答 4

3

async/await关键字完全符合您的要求,非常优雅。

这是我将您的方法转换为正确模式的方法:

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;
}

然后你会做这样的事情:

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;
}
于 2013-01-13T00:11:13.160 回答
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);
}

用法,例如,通过一个按钮的点击:

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

正如作者所要求的,它不会阻塞原始线程。如果您必须通过回调在 UI 上进行操作,请小心,请记住使用Invoke和它的等价物。

编辑:阅读 SiLo 的评论,了解一些可以应用于上述代码的良好实践和优化。

于 2013-01-13T00:12:30.960 回答
2

我刚刚看到您关于使用 3.5 而不是 4.5 的编辑。这太糟糕了,但绝对还是有可能的。我已经创建了第二个答案,因此使用async/的人await可以使用第一个作为示例。

现在对于您的解决方案,实际上并没有太大的不同:

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);
  });
}
于 2013-01-13T00:26:21.230 回答
0

抱歉,但是从逻辑上讲,您在这里尝试的方法不太聪明。

  • 你想截图。
  • 你不希望 UI 线程阻塞,所以你去异步。

祝贺。到目前为止,这是有道理的。

现在是您不想告诉任何人您尝试过的部分:

  • 现在您想在 UI 线程中等待异步操作完成。

回到开始 - 你阻塞了 UI 线程。什么都没有实现。从逻辑上讲,您基本上会在与开始时完全相同的地方结束。

好的,解决方案:

  • 首先,摆脱线程,使用任务。更高效。
  • 其次,要意识到在 UI 线程中等待是没有意义的。停用 UI 元素,然后在处理结束时重新打开它们。

将此作为状态机问题处理(UI 处于“工作”或“等待命令”状态),因此您不会阻塞。这是处理该问题的唯一方法 - 因为最后如果您等待执行完成,整个异步操作将毫无用处。

你不能启动一个方法,然后等待处理完成阻塞线程——如果你曾经尝试过,那么整个异步操作就是一个无用的提议。

于 2013-01-13T00:01:32.367 回答