2013-01-12 315 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); 
    }); 
}