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

4.5 の代わりに 3.5 を使用することについてのあなたの編集を見たところです。それはあまりにも悪いことですが、それは間違いなくまだ可能です。async/awaitを使用している人が最初の回答を例として使用できるように、この 2 番目の回答を作成しました。

今あなたのソリューションでは、実際にはそれほど違いはありません:

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 スレッドをブロックします。何も達成されません。基本的に、論理的には、最初とまったく同じ場所に行き着きます。

わかりました、解決策:

  • まず、スレッドを取り除き、Task を使用します。もっと効率的。
  • 次に、UI スレッドでは待機が意味をなさないことを理解してください。UI 要素を非アクティブ化し、処理の最後に再びオンにします。

これをステート マシンの問題 (UI が「作業中」または「コマンド待ち」状態) として処理し、ブロックしないようにします。これがそれを処理する唯一の方法です。実行が完了するのを待つと、最後に非同期処理全体が役に立たなくなるためです。

メソッドを開始してから、処理がスレッドのブロックを完了するのを待つことはできません。それを試みると、非同期操作全体が役に立たない命題になります。

于 2013-01-13T00:01:32.367 に答える