15

Các câu hỏi mới về người mới:Tại sao mã Parallel.ForEach này đóng băng chương trình?

Mã này lấy một số proxy từ danh sách trong cửa sổ chính (tôi không thể tìm ra cách làm cho các biến có sẵn giữa các chức năng khác nhau) và kiểm tra từng biến httpwebrequest) và sau đó thêm chúng vào một danh sách gọi là finishedProxies.

Vì một lý do nào đó khi tôi nhấn nút bắt đầu, toàn bộ chương trình sẽ bị treo. Tôi đã theo ấn tượng rằng Parallel tạo ra các luồng riêng biệt cho mỗi hành động rời khỏi thread UI một mình sao cho nó đáp ứng?

private void start_Click(object sender, RoutedEventArgs e) 
     { 
      // Populate a list of proxies 
      List<string> proxies = new List<string>(); 
      List<string> finishedProxies = new List<string>(); 

      foreach (string proxy in proxiesList.Items) 
      { 
       proxies.Add(proxy); 
      } 

      Parallel.ForEach<string>(proxies, (i) => 
      { 
       string checkResult; 
       checkResult = checkProxy(i); 

       finishedProxies.Add(checkResult); 
       // update ui 
       /* 
       status.Dispatcher.Invoke(
        System.Windows.Threading.DispatcherPriority.Normal, 
        new Action(
        delegate() 
        { 
         status.Content = "hello" + checkResult; 
        } 
       )); */ 
       // update ui finished 


       //Console.WriteLine("[{0}] F({1}) = {2}", Thread.CurrentThread.Name, i, CalculateFibonacciNumber(i)); 
      }); 


     } 

Tôi đã thử sử dụng mã được nhận xét để thực hiện thay đổi đối với giao diện người dùng bên trong Parallel.Foreach và làm cho chương trình bị đóng băng sau khi nút bắt đầu được nhấn. Nó đã làm việc cho tôi trước đây nhưng tôi đã sử dụng lớp Thread.

Làm cách nào để cập nhật giao diện người dùng từ bên trong Parallel.Foreach và làm cách nào để thực hiện công việc Parallel.Foreach để nó không làm cho giao diện người dùng bị đóng băng khi đang hoạt động?

Here's the whole code.

+2

Bạn đang pummelling thread UI với yêu cầu gọi, nó không nhận được xung quanh để làm nhiệm vụ thường xuyên của nó nữa. Giống như sơn lại giao diện người dùng. Ít nhất thấp hơn mức ưu tiên cho Nền. –

+0

@ dsp_099б ý của bạn là gì dưới "Nó đã làm việc cho tôi trước đây nhưng tôi đã sử dụng lớp Thread"? – Fulproof

Trả lời

15

Bạn không được bắt đầu xử lý song song trong chuỗi giao diện người dùng của mình. Xem ví dụ dưới tiêu đề "Tránh thực hiện song song vòng lặp trên tiêu đề giao diện người dùng" trong this page.

Cập nhật: Hoặc, bạn có thể chỉ cần tạo manuall luồng mới và bắt đầu xử lý bên trong như tôi thấy bạn đã làm. Không có gì sai với điều đó nữa.

Ngoài ra, như Jim Mischel chỉ ra, bạn đang truy cập vào danh sách từ nhiều chủ đề cùng một lúc, do đó, có điều kiện chủng tộc ở đó. Hoặc thay thế ConcurrentBag cho List hoặc bọc các danh sách bên trong một tuyên bố lock mỗi khi bạn truy cập chúng.

+4

Bạn có ý nghĩa gì dưới "thay thế' ConcurrentBag' cho 'Danh sách'"? ... Bạn có thực sự có nghĩa là "thay thế' Danh sách' thành 'ConcurrentBag'" không? – Fulproof

+1

@Fulproof: Tôi tin rằng tiếng Anh chính xác là "thay thế A * cho * B" == "thay thế B * bằng * A". – Jon

+1

["thay thế một điều (A) cho cái khác (B)"] (http://dictionary.reverso.net/english-cobuild/to%20substitute%20a%20for%20b) == "thay thế A bằng B" = = (A) "diễn ra hoặc thực hiện chức năng của" B "khác. Bạn có thể đưa ra bất kỳ tham chiếu nào về việc sử dụng của bạn không? – Fulproof

1

Một vấn đề với mã của bạn là bạn đang gọi điện thoại FinishedProxies.Add từ nhiều luồng đồng thời. Điều đó sẽ gây ra sự cố vì List<T> không an toàn cho chủ đề. Bạn sẽ cần phải bảo vệ nó bằng khóa hoặc một số nguyên gốc đồng bộ hóa khác hoặc sử dụng một bộ sưu tập đồng thời.

Cho dù điều đó gây ra sự khóa giao diện người dùng, tôi không biết. Không có thêm thông tin, thật khó để nói. Nếu danh sách proxies rất dài và checkProxy không mất nhiều thời gian để thực thi, khi đó nhiệm vụ của bạn sẽ được xếp sau cuộc gọi Invoke. Điều đó sẽ gây ra một loạt các cập nhật giao diện người dùng đang chờ xử lý. Điều đó sẽ khóa giao diện người dùng vì luồng giao diện người dùng đang bận phục vụ các yêu cầu được xếp hàng đợi đó.

+0

Bạn có thể cho tôi biết thêm về cách làm cho danh sách chỉ an toàn không? –

+0

Ngoài ra, tôi tải khoảng 10 proxy mỗi lần để chạy thử nghiệm vì vậy nó không quá nhiều; Tôi đã thêm một dòng SAU song song. Cho phép thay đổi hộp nhãn thành 'người kiểm tra đã hoàn tất!' và khi tôi nhấn nút kiểm tra chương trình WAITS cho đến khi tất cả các quy trình được thực hiện trước khi cập nhật hộp sao cho nó giống như có tất cả chúng chạy đồng thời nhưng nó giống như chúng chạy đồng thời trong cùng một luồng nếu điều đó có ý nghĩa, httpwebrequest từ cùng một chuỗi ui sẽ làm cho nó treo lên chính xác theo cùng một cách. –

2

Nếu có ai đó tò mò, tôi đã tìm ra nhưng tôi không chắc đó có phải là một chương trình hay hay cách nào để giải quyết vấn đề này.

Tôi tạo ra một chủ đề mới như vậy:

Thread t = new Thread(do_checks); 
t.Start(); 

và dứt tất cả những thứ song song bên trong do_checks().

Dường như đang hoạt động tốt.

1

Đây là những gì tôi nghĩ có thể xảy ra trong cơ sở mã của bạn.

Tình huống bình thường: Bạn nhấp vào nút. Không sử dụng vòng lặp Parallel.Foreach.Sử dụng lớp Dispatcher và đẩy mã để chạy trên thread riêng biệt trong nền. Khi chuỗi nền được xử lý xong, nó sẽ gọi luồng giao diện người dùng chính để cập nhật giao diện người dùng. Trong trường hợp này, luồng nền (được gọi thông qua Dispatcher) biết về chuỗi giao diện người dùng chính, mà nó cần gọi lại. Hoặc đơn giản là chuỗi giao diện người dùng chính có bản sắc riêng của nó.

Sử dụng vòng lặp Parallel.Foreach: Khi bạn gọi Paralle.Foreach loop, khung công tác sử dụng luồng threadpool. ThreadPool chủ đề được chọn ngẫu nhiên và mã thực thi không bao giờ nên làm cho bất kỳ giả định về danh tính của chủ đề được lựa chọn. Trong mã ban đầu của nó rất nhiều có thể là chủ đề dispatcher gọi qua Parallel.Foreach vòng lặp là không thể tìm ra các chủ đề mà nó được liên kết với. Khi bạn sử dụng thread rõ ràng, sau đó nó hoạt động tốt bởi vì các thread rõ ràng có bản sắc riêng của mình mà có thể được dựa vào bởi các mã thực thi. Lý tưởng nhất là nếu mối quan tâm chính của bạn là giữ giao diện người dùng, thì trước tiên bạn nên sử dụng lớp Dispatcher để đẩy mã trong chuỗi nền và sau đó sử dụng logic bao giờ bạn muốn tăng tốc thực thi tổng thể.

6

Một cách hay để phá vỡ các vấn đề không thể ghi vào chuỗi giao diện người dùng khi sử dụng câu lệnh song song là sử dụng Task Factory và đại biểu, xem mã sau, tôi sử dụng điều này để lặp qua một loạt tệp một thư mục, và quá trình này, chúng trong một vòng lặp foreach song song, sau mỗi tập tin được xử lý thread UI được báo hiệu và Cập nhật:

var files = GetFiles(directoryToScan); 

tokenSource = new CancellationTokenSource(); 
CancellationToken ct = tokenSource.Token; 

Task task = Task.Factory.StartNew(delegate 
{ 
    // Were we already canceled? 
    ct.ThrowIfCancellationRequested(); 

    Parallel.ForEach(files, currentFile => 
    { 
     // Poll on this property if you have to do 
     // other cleanup before throwing. 
     if (ct.IsCancellationRequested) 
     { 
      // Clean up here, then... 
      ct.ThrowIfCancellationRequested(); 
     } 

     ProcessFile(directoryToScan, currentFile, directoryToOutput); 

     // Update calling thread's UI 
     BeginInvoke((Action)(() => 
     { 
      WriteProgress(currentFile); 
     })); 
    }); 
}, tokenSource.Token); // Pass same token to StartNew. 

task.ContinueWith((t) => 
     BeginInvoke((Action)(() => 
     { 
      SignalCompletion(sw); 
     })) 
); 

và các phương pháp làm thay đổi giao diện người dùng thực tế:

void WriteProgress(string fileName) 
{ 
    progressBar.Visible = true; 
    lblResizeProgressAmount.Visible = true; 
    lblResizeProgress.Visible = true; 

    progressBar.Value += 1; 
    Interlocked.Increment(ref counter); 
    lblResizeProgressAmount.Text = counter.ToString(); 

    ListViewItem lvi = new ListViewItem(fileName); 
    listView1.Items.Add(lvi); 
    listView1.FullRowSelect = true; 
} 

private void SignalCompletion(Stopwatch sw) 
{ 
    sw.Stop(); 

    if (tokenSource.IsCancellationRequested) 
    { 
     InitializeFields(); 
     lblFinished.Visible = true; 
     lblFinished.Text = String.Format("Processing was cancelled after {0}", sw.Elapsed.ToString()); 
    } 
    else 
    { 
     lblFinished.Visible = true; 
     if (counter > 0) 
     { 
      lblFinished.Text = String.Format("Resized {0} images in {1}", counter, sw.Elapsed.ToString()); 
     } 
     else 
     { 
      lblFinished.Text = "Nothing to resize"; 
     } 
    } 
} 

Hope điều này có ích!

+0

Bạn có thể tăng thêm hiệu năng bằng cách sử dụng 'BeginInvoke' trên luồng giao diện người dùng, sau đó bạn không phải chờ trong khi cập nhật, như bạn hiện đang làm khi sử dụng' Gọi '. Tất nhiên, điều này có thể yêu cầu các khóa bên trong 'WriteProgress' ... –

+0

Chuỗi giao diện người dùng hiện không bị khóa, nó vẫn đáp ứng đầy đủ, vì vậy tôi không chắc chắn điều này có thể hữu ích như thế nào? Nhưng tôi sẽ thử xem sự khác biệt, tự khóa chính các đối tượng không phải là vấn đề trong kịch bản của tôi. – StevenVL

+0

Bỏ qua nhận xét của tôi về khóa - Tôi đang nghĩ về nhiều luồng đang chạy 'BeginInvoke', nhưng vì tất cả chúng đều được gọi trên luồng giao diện người dùng, nên không thể có bất kỳ sự ủy nhiệm lại nào. Những gì tôi đã nói là mỗi thread trong 'Parallel.ForEach' của bạn phải chờ đợi' Invoke' để hoàn thành, điều này sẽ làm chậm mọi thứ. Với 'BeginInvoke', các cập nhật giao diện người dùng sẽ được xếp hàng đợi trên luồng giao diện người dùng và sẽ chạy không đồng bộ. –

0

nếu bạn muốn sử dụng song song foreach trong giao diện điều khiển nút bấm như vv sau đó đặt foreach song song trong Task.Factory.StartNew như

private void start_Click(object sender, EventArgs e) 
     { 
       await Task.Factory.StartNew(() => 
        Parallel.ForEach(YourArrayList, (ArraySingleValue) => 
        { 

       Console.WriteLine("your background process code goes here for:"+ArraySingleValue); 
        }) 
        ); 
    }//func end 

nó sẽ giải quyết đóng băng/khó khăn hay treo vấn đề

Các vấn đề liên quan