2010-02-28 38 views
6

Tôi đang cố gắng làm cho ứng dụng C# của tôi đa luồng bởi vì đôi khi, tôi nhận được một ngoại lệ cho biết tôi đã thực hiện cuộc gọi đến một chuỗi theo cách không an toàn. Tôi đã không bao giờ thực hiện bất kỳ đa luồng trước khi trong một chương trình, vì vậy chịu với tôi nếu tôi âm thanh kinda dốt nát về vấn đề này.Cuộc gọi đa luồng trong ứng dụng Windows Forms?

Tổng quan về chương trình của tôi là tôi muốn tạo một ứng dụng giám sát hiệu suất. Điều này đòi hỏi phải sử dụng lớp truy cập quy trình và hiệu năng trong C# để khởi động và theo dõi thời gian xử lý của ứng dụng và gửi số đó trở lại giao diện người dùng. Tuy nhiên, trong phương thức thực sự gọi phương thức nextValue của bộ đếm hiệu suất (được thiết lập để thực hiện mỗi giây nhờ một bộ đếm thời gian), đôi khi tôi sẽ nhận được ngoại lệ nói trên mà sẽ nói về việc gọi một luồng không an toàn.

Tôi đã đính kèm một số mã cho sự nhìn chăm chú của bạn. Tôi biết đây là một câu hỏi tiêu tốn thời gian, vì vậy tôi sẽ rất biết ơn nếu có ai có thể giúp tôi bất cứ nơi nào để tạo ra một sợi chỉ mới và cách gọi nó một cách an toàn. Tôi đã cố gắng nhìn vào những gì đã được lên trên MSDN, nhưng điều đó chỉ kinda nhầm lẫn tôi.

private void runBtn_Click(object sender, EventArgs e) 
{ 
    // this is called when the user tells the program to launch the desired program and 
    // monitor it's CPU usage. 

    // sets up the process and performance counter 
    m.runAndMonitorApplication(); 

    // Create a new timer that runs every second, and gets CPU readings. 
    crntTimer = new System.Timers.Timer(); 
    crntTimer.Interval = 1000; 
    crntTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent); 
    crntTimer.Enabled = true; 
} 

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    // get the current processor time reading 
    float cpuReading = m.getCPUValue(); 

    // update the current cpu label 
    crntreadingslbl.Text = cpuReading.ToString(); // 

} 
// runs the application 
public void runAndMonitorApplication() 
{ 
    p = new Process(); 
    p.StartInfo.UseShellExecute = true; 
    p.StartInfo.CreateNoWindow = true; 
    p.StartInfo.FileName = fileName; 
    p.Start(); 

    pc = new System.Diagnostics.PerformanceCounter("Process", 
       "% Processor Time", 
       p.ProcessName, 
       true); 
} 

// This returns the current percentage of CPU utilization for the process 
public float getCPUValue() 
{ 
    float usage = pc.NextValue(); 

    return usage; 
} 

Trả lời

7

Kiểm tra bài viết của Jon Skeet về đa luồng, đặc biệt là trang trên multi-threading winforms. Nó sẽ sửa chữa bạn ngay lập tức.

Về cơ bản, bạn cần phải kiểm tra xem liệu có cần phải gọi hay không và sau đó thực hiện cuộc gọi nếu cần. Sau khi đọc bài viết, bạn sẽ có thể cấu trúc lại mã giao diện người dùng của mình thành các khối trông giống như sau:

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    // get the current processor time reading 
    float cpuReading = m.getCPUValue(); 

    if (InvokeRequired) 
    { 
     // We're not in the UI thread, so we need to call BeginInvoke 
     BeginInvoke(new Action(() => crntreadingslbl.Text = cpuReading.ToString())); 
     return; 
    } 
    // Must be on the UI thread if we've got this far 
    crntreadingslbl.Text = cpuReading.ToString(); 
} 

Trong mã của bạn, yêu cầu sẽ được yêu cầu vì bạn đang sử dụng bộ hẹn giờ. Theo tài liệu dành cho System.Timers.Timer:

Sự kiện đã hết được nêu lên trên chuỗi ThreadPool.

Điều này có nghĩa là phương thức OnTimedEvent() mà bạn đặt làm đại biểu của bộ hẹn giờ sẽ thực hiện trên chuỗi ThreadPool có sẵn tiếp theo, chắc chắn sẽ không phải là chuỗi giao diện người dùng của bạn. Các tài liệu cũng cho thấy một cách thay thế để giải quyết vấn đề này:

Nếu bạn sử dụng Timer với một yếu tố giao diện người dùng , chẳng hạn như một hình thức hoặc kiểm soát, chuyển nhượng dưới hình thức hoặc điều khiển chứa Timer để các SynchronizingObject thuộc tính, để sự kiện được sắp xếp theo thứ tự giao diện người dùng .

Bạn có thể thấy tuyến đường này dễ dàng hơn, nhưng tôi chưa thử.

+0

Được rồi, đây và nhận xét công nhân nền dường như khá hữu ích; nhưng như tôi đã hiểu, bản thân quy trình đang chạy trên chuỗi giao diện người dùng, nhưng tôi phải tạo một luồng riêng để thu thập và cập nhật dữ liệu về quy trình đó? Nói chung, làm thế nào tôi sẽ có thể cho biết nơi để làm cho một chủ đề riêng biệt? – Waffles

+0

Bộ hẹn giờ sẽ thực hiện ủy nhiệm ElapsedEventHandler được yêu cầu trên luồng ThreadPool đầu tiên có sẵn khi bộ hẹn giờ "tắt". Vì vậy, bất cứ điều gì bạn yêu cầu bộ đếm thời gian để làm sẽ xảy ra trên một chủ đề riêng biệt, không phải trên thread UI. Thêm một nhân viên nền sẽ chỉ giới thiệu một chủ đề khác cho phương trình. –

0

Vấn đề của bạn, tôi nghĩ, đó là dòng này:

crntreadingslbl.Text = cpuReading.ToString(); 

Đang chạy bên ngoài của thread UI. Bạn không thể cập nhật phần tử giao diện người dùng bên ngoài chuỗi giao diện người dùng. Bạn cần gọi Invoke trên Window để gọi một phương thức mới trên thread UI.

Tất cả những gì đã nói, tại sao không sử dụng perfmon? Nó được xây dựng cho mục đích.

0

Thành phần BackGroundWorker có thể giúp bạn. Nó có sẵn trên hộp công cụ để bạn có thể kéo đến biểu mẫu của mình.

Thành phần này hiển thị một tập hợp các sự kiện để thực hiện các tác vụ trong một chuỗi khác với chuỗi giao diện người dùng. Bạn không phải lo lắng về việc tạo một chủ đề.

Tất cả tương tác giữa mã chạy trên nền và các điều khiển giao diện người dùng phải được thực hiện thông qua trình xử lý sự kiện.

Đối với kịch bản của bạn, bạn có thể thiết lập bộ hẹn giờ để kích hoạt nhân viên nền tại một khoảng thời gian cụ thể.

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    backgroundWorker.RunWorkerAsync(); 
} 

Sau đó, bạn thực hiện các xử lý sự kiện thích hợp để thực sự thu thập dữ liệu và cập nhật giao diện người dùng

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    // Collect performance data and update the UI 
} 
Các vấn đề liên quan