2013-01-12 363 views
3

嗯,我有一个功能,需要从电脑的屏幕截图,但不幸的是,它阻止主用户界面,所以我决定做一个异步[Threaded调用]它;但是,我觉得麻烦等待在返回位图之前Thread的结果。异步等待一个线程完成

这里是我的代码:

/// <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]

并提前致谢。这里

问题夏娃和料仓的帮助下解决是最好的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!")); 
} 
+0

您在使用.NET 4.5? – Mir

+0

不,先生,我不能使用它,我只是想在.Net 3.5上的解决方案 –

回答

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的一些良好实践和优化的评论,您可以将其应用于上述代码。

+1

使用ThreadPool线程比每次创建一个新的Thread都更好。 调用'Graphics.Dispose()'和'Bitmap.Dispose()'也是一个好习惯,以避免沉重的内存泄漏,尤其是在处理32百万像素的32bpp图像时。 – Erik

+0

@SiLo同意你带来的所有观点。该示例的目的只是将作者介绍给EAP,而不会更改其代码。我会编辑我的文章,并使其指向您的评论。 – Mir

+0

@SiLo你能提供一个在我的情况下使用ThreadPool的例子,因为我对此毫无头绪。 –

0

对不起,但在逻辑上是不是太聪明了什么你在这里尝试。

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

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

现在到你不想告诉任何人的一部分,你试过:

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

而后面我们将开始 - 您屏蔽了UI线程。没有任何成就。你基本上在逻辑上结束于你刚开始的同一个地方。

好了,解决方法:

  • 首先,摆脱了线的,使用任务。更高效。
  • 其次,意识到等待在UI线程中没有意义。取消激活UI元素,然后在处理结束时将其重新打开。

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

你不能启动一个方法,然后等待处理完成阻塞线程 - 如果你尝试过,那么整个异步操作就是没用的命题。

+0

+1为什么人们downvote没有评论? –

+1

汤姆爵士我真正想要做的就是拍摄屏幕截图,而不会阻塞主UI线程,特别是我会在一分钟内拍摄数百个镜头,这些东西会使UI完全冻结,所以我想要在在有效的方式背景。我想过后台工作人员,但我不喜欢使用它。所以简单地说,我想要一种方式来等待线程完成它的工作,然后返回结果... –

+0

正如我所说 - 你不能在UI线程中做到这一点。你必须采取状态机方法。这样简单,现实并不在乎你是否喜欢它。 – TomTom

2

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

他明确表示他不能使用.NET 4.5。 – Mir

+0

当我发布他的编辑还没有。不用担心,我已经在下面发布了3.5解决方案。 – Erik

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