2017-03-04 50 views
-1

[已解決] Ofir的解決方案開箱即用。目前以24 f/s運行五分鐘。Newb over head:爲什麼我的自制屏幕錄像機崩潰?(c#和windows窗體)

我試圖從屏幕保存圖像。

基本上我的問題是,當我減少Thread.sleep(創建一個體面的幀速率)程序崩潰。程序崩潰的速度越快thread.sleep越接近0,但我甚至找不到這個問題,因爲我從來沒有處理過這種東西(如果是unity3d,我會完成這個)。

void ScreenCapture()//this is from a youtube tutorial 
    { 
     while (true) 
     { 
      Bitmap bm = new Bitmap((int)Math.Round(Screen.PrimaryScreen.Bounds.Width * 1.5), (int)Math.Round(Screen.PrimaryScreen.Bounds.Height * 1.5)); 
    //I don't know why I had to multipyly these by 1.5, but it made the screen fit properly. 
      Graphics g = Graphics.FromImage(bm); 
      g.CopyFromScreen(0, 0, 0, 0, bm.Size); 
      pictureBox.Image = bm; 
      Thread.Sleep(250); 
     } 
    } 

    private void button2_Click(object sender, EventArgs e) 
    { 
     Thread t = new Thread(ScreenCapture); 
     t.Start(); 
    } 

我也有一些甜蜜的甜蜜的錯誤來了你的方式。

型 'System.ComponentModel.Win32Exception' 未處理的異常發生在System.Drawing.dll程序

錯誤代碼:2147467259

和我建立一個try-catch(我不太瞭解)但經過幾次測試後它也會中斷,讓我看看我是否可以得到這個日誌。

System.ComponentModel.Win32Exception(0x80004005): The operation completed successfully as System.Drawing.Graphics.CopyFromScreen(Int32 sourceX, Int32 sourceY, Int32 destinationX,Int32 destinationY, Size blockRegionSize) 
at Screen_Recorder.Form1.ScreenCapture() in C:Users\Jupiter\Desktop\visual studio experiments\Screen Recorder\ScreenRecorder\Form1.cs:line 35 

如果我然後單擊確定它說:

System.ArgumentException: Parameter is not valid. 
at System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format) 
at System.Drawing.Bitmap..ctor(int32 width, Int32 height) 
at Screen_Recorder.Form1.ScreenCapture() C:Users\Jupiter\Desktop\visual studio experiments\Screen Recorder\ScreenRecorder\Form1.cs:line 32 

,然後它會重複錯誤循環往復。

無論如何,我要睡覺,然後我會再次進入明天,但在此之前的任何建議將非常感激!

回答

1

您有內存泄漏。

GraphicsBitmap是一次性使用的物品,應在處理完它們後丟棄。
您只能將Graphics放在using塊中,並且最後一個位圖應在循環的每次迭代中放置。
因此,你的代碼應該是這樣的:

private void ScreenCapture() 
{ 
    while (true) 
    { 
     var bm = new Bitmap((int)Math.Round(Screen.PrimaryScreen.Bounds.Width * 1.5), 
          (int)Math.Round(Screen.PrimaryScreen.Bounds.Height * 1.5)); 
     using (Graphics g = Graphics.FromImage(bm)) g.CopyFromScreen(0, 0, 0, 0, bm.Size); 

     // As per the comment by HansPassant - the following would cause 
     // a thread race with the UI thread. 
     //this.pictureBox1.Image?.Dispose(); 
     //this.pictureBox1.Image = bm; 

     // Instead we use beginInvoke to run this on the UI thread 
     Action action =() => 
      { 
       this.pictureBox1.Image?.Dispose(); 
       this.pictureBox1.Image = bm; 
      }; 

     this.BeginInvoke(action); 

     Thread.Sleep(250); 
    } 
} 

這個循環永遠不會結束(因爲while (true)),也許你應該考慮增加一個CancelationToken和使用Task代替Thread的。

還有最後一件事,你應該在完成它之後處置picturebox本身。

+0

這是一個原因,一個非常普遍的原因,但不是唯一的原因。 Image屬性不是線程安全的,並且在UI線程正在繪製圖像的同時調用Dispose()將不會有好結果。失敗的可能性要低得多,但不是零,因此更難調試。 –

+0

@HansPassant我不知道我完全理解你,但如果我們保存對圖像的引用,設置新圖像然後丟棄舊圖像呢?會解決這個問題嗎? –

+0

不,這仍然是一個線程競爭,因爲Paint()方法可能正在運行並且正在使用舊引用。冷酷的事實是,您必須使用BeginInvoke(),以便代碼在UI線程上運行。 –