2011-07-01 34 views
6

Trong mã bên dưới, tôi muốn đồng bộ hóa báo cáo kết quả của danh sách tác vụ. Điều này đang làm việc ngay bây giờ bởi vì task.Result khối cho đến khi nhiệm vụ hoàn thành. Tuy nhiên, nhiệm vụ id = 3 mất nhiều thời gian để hoàn thành và chặn tất cả các tác vụ đã hoàn thành khác khỏi báo cáo trạng thái của chúng. Tôi nghĩ rằng tôi có thể làm điều này bằng cách di chuyển báo cáo (Console.Write) vào một lệnh .ContinueWith nhưng tôi không có một chuỗi giao diện người dùng vì vậy làm thế nào để tôi nhận được một TaskScheduler để đồng bộ hóa các tác vụ .ContinueWith?Đồng bộ hóa tác vụ mà không có chuỗi giao diện người dùng

Những gì tôi có bây giờ:

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    var tasks = new List<Task<int>>(); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     var t = Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 5000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }); 
     tasks.Add(t); 
    } 

    foreach (var task in tasks) 
    { 
     Console.WriteLine("Completed {0} on {1}", task.Result, Thread.CurrentThread.ManagedThreadId); 
    } 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 

Tôi muốn chuyển sang này hoặc cái gì đó tương tự nhưng tôi cần những Console.Write ("Đã hoàn thành ...") cho tất cả xảy ra trên cùng một sợi:

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }).ContinueWith(value => 
     { 
      Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId); 
     } 

    /* need syncronization context */); 
    } 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 

- gIẢI PHÁP - Sau khi nhận được một số ý kiến ​​và đọc một số các giải pháp này là giải pháp hoàn chỉnh mà những gì tôi muốn. Mục tiêu ở đây là xử lý các tác vụ chạy dài của severl nhanh nhất có thể và sau đó thực hiện điều gì đó với kết quả của từng tác vụ một tại một thời điểm.

static void Main(string[] args) 
{ 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 

    var results = new BlockingCollection<int>(); 

    Task.Factory.StartNew(() => 
    { 
     while (!results.IsCompleted) 
     { 
      try 
      { 
       var x = results.Take(); 
       Console.WriteLine("Completed {0} on {1}", x, Thread.CurrentThread.ManagedThreadId); 
      } 
      catch (InvalidOperationException) 
      { 
      } 
     } 
     Console.WriteLine("\r\nNo more items to take."); 
    }); 

    var tasks = new List<Task>(); 

    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     var t = Task.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      results.Add(num); 
     }); 

     tasks.Add(t); 
    } 

    Task.Factory.ContinueWhenAll(tasks.ToArray(), _ => results.CompleteAdding()); 

    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 
+0

tôi giả thread Console là một stand-in cho một GUI (WinForms/WPF). Đó không phải là một ý tưởng hay, sự hiện diện của một Dispatcher/Messageloop tạo nên sự khác biệt lớn. –

+1

Nếu không, hãy nghĩ về ý nghĩa của bạn về "xảy ra trên cùng một chuỗi". Không thể làm điều đó trừ khi chủ đề đó đang bỏ phiếu. –

+0

Tôi cần phải thay đổi nó để chỉ một nhiệm vụ ContinueWith chạy cùng một lúc. Nó không quan trọng nếu họ chạy trên cùng một sợi hay không nhưng tôi không thể có hai trong số họ chạy song song. Trong cuộc sống thực, tôi phần ContinueWith đang viết rất nhiều dữ liệu vào một cơ sở dữ liệu. –

Trả lời

1

Bạn sẽ phải tạo một nhiệm vụ tác giả của một số loại, tuy nhiên, hãy ghi nhớ thậm chí nhiệm vụ này có thể được dời lại vào một thread mẹ đẻ hoặc quản lý! Sử dụng bộ lập lịch mặc định trong TPL bạn không có quyền kiểm soát đối với luồng được quản lý nào nhận được công việc.

public class ConcurrentConsole 
{ 
    private static BlockingCollection<string> output 
     = new BlockingCollection<string>(); 

    public static Task CreateWriterTask(CancellationToken token) 
    { 
     return new Task(
      () => 
      { 
       while (!token.IsCancellationRequested) 
       { 
        string nextLine = output.Take(token); 
        Console.WriteLine(nextLine); 
       } 
      }, 
      token); 
    } 

    public static void WriteLine(Func<string> writeLine) 
    { 
     output.Add(writeLine()); 
    } 
} 

Khi tôi chuyển mã của bạn để sử dụng này, tôi nhận được kết quả như sau:

End of Main 
Done 1 on 6 
Completed 1 on 6 
Done 5 on 9 
Completed 5 on 9 
Done 0 on 4 
Completed 0 on 4 
Done 2 on 5 
Completed 2 on 13 
Done 7 on 10 
Completed 7 on 10 
Done 4 on 8 
Completed 4 on 5 
Done 9 on 12 
Completed 9 on 9 
Done 6 on 6 
Completed 6 on 5 
Done 8 on 11 
Completed 8 on 4 
Done 3 on 7 
Completed 3 on 7 

Ngay cả với mã của bạn gửi () => String.Format("Completed {0} on {1}"...-ConcurrentConsole.WriteLine, đảm bảo ManagedThreadId sẽ được nhặt trên ConcurrentConsole Task, nó vẫn sẽ thay đổi chuỗi nó chạy trên đó. Mặc dù có ít biến đổi hơn so với các nhiệm vụ thực hiện.

+0

Điều này thực sự tồi tệ hơn vòng lặp nhiệm vụ foreach của tôi. Điều này chờ đợi cho tất cả các nhiệm vụ để hoàn thành trước khi báo cáo kết quả của bất kỳ của họ. Những gì tôi đang tìm kiếm là một cách để báo cáo hoàn thành nhiệm vụ ngay sau khi nó được thực hiện và có báo cáo syncronized đến một sợi. Tôi không muốn một nhiệm vụ chạy dài để chặn người khác báo cáo. –

+0

@Ryan: Tôi đã thêm báo cáo vào một Nhiệm vụ cụ thể, đó là điều tốt nhất bạn có thể đảm bảo trong TPL. – user7116

+0

Đây không phải là cách tôi triển khai thực hiện hệ thống nhưng ý tưởng sử dụng BlockingCollection là cốt lõi của việc triển khai. –

0

tôi sẽ đề nghị:

1) Tạo một đối tượng khóa
2) Tạo một danh sách các chuỗi được viết
3) đẻ trứng một sợi mà vòng, ngủ một chút, sau đó khóa danh sách các chuỗi, sau đó nếu nó không rỗng, viết tất cả chúng và làm rỗng danh sách
4) Các chủ đề khác sau đó khóa danh sách, thêm trạng thái, mở khóa và tiếp tục.

object writeListLocker = new object(); 
List<string> linesToWrite = new List<string>(); 

// Main thread loop 
for (; ;) 
{ 
    lock (writerListLocker) 
    { 
     foreach (string nextLine in linesToWrite) 
      Console.WriteLine(nextLine); 
     linesToWrite.Clear(); 
    } 
    Thread.Sleep(500); 
} 

// Reporting threads 
lock (writerListLocker) 
{ 
    linesToWrite.Add("Completed (etc.)"); 
} 
+0

Tôi muốn tiếp tục sử dụng TPL nếu tôi có thể. Nó không giống như ở trên là sẽ cho phép xử lý xảy ra trên một loạt các chủ đề trong khi các văn bản của các kết quả được syncronized trên một sợi. –

+0

Nhưng đó chính xác là điều này. Vòng lặp luồng chính thực hiện tất cả các văn bản. Các chủ đề khác đều thêm vào đối tượng linesToWrite thay vì viết. –

1

Bạn có thể sử dụng OrderedTaskScheduler để đảm bảo chỉ hoàn thành một tác vụ tại một thời điểm; Tuy nhiên, chúng sẽ chạy trên một thread threadpool (không nhất thiết phải tất cả trên cùng một thread).

Nếu bạn thực sự cần tất cả trên cùng một chuỗi (không chỉ một lần), thì bạn có thể sử dụng ActionThread từ Nito.Async library. Nó cung cấp một mã số SynchronizationContext, có thể được chọn bởi FromCurrentSynchronizationContext.

+0

Đây là hướng mà tôi muốn đi. Tôi muốn đưa các hoạt động ContinueWith vào một chuỗi duy nhất để đảm bảo rằng chỉ một trong số chúng chạy cùng một lúc. Không chắc chắn nếu tôi có thể sử dụng OrderedTaskScheduler trong sản xuất mặc dù. Tôi muốn sử dụng FromCurrentSynchronizationContext nhưng tôi không thể vì tôi đang ở trong một môi trường dịch vụ (a.k.a. no UI thread) và SyncronizationContext.Current đang trả về null. –

+0

Tôi xem cả 'OrderedTaskScheduler' và' ActionThread' là chất lượng sản xuất. Có vẻ như một trong hai người trong số họ có thể giải quyết vấn đề của bạn. –

0

Tôi nghĩ bạn mong đợi kết quả như sau.

Starting on 8 
Done 1 on 11 
Completed 1 on 9 
Done 5 on 11 
Completed 5 on 9 
Done 0 on 10 
Completed 0 on 9 
Done 2 on 12 
Completed 2 on 9 
Done 7 on 16 
Completed 7 on 9 
Done 4 on 14 
Completed 4 on 9 
Done 9 on 18 
Completed 9 on 9 
Done 6 on 15 
Completed 6 on 9 
Done 8 on 17 
Completed 8 on 9 
Done 3 on 13 
Completed 3 on 9 

Như dưới đây, tôi đã sử dụng StaSynchronizationContext trong mã của tôi từ the Understanding SynchronizationContext nơi một cuộc gọi đồng bộ trong một thread được giải thích tốt. Vui long tham khảo thông tin đo.

đoạn mã của tôi là:

static void Main(string[] args) 
{ 
    StaSynchronizationContext context = new StaSynchronizationContext(); 
    StaSynchronizationContext.SetSynchronizationContext(context); 
    Console.WriteLine("Starting on {0}", Thread.CurrentThread.ManagedThreadId); 
    for (var i = 0; i < 10; i++) 
    { 
     var num = i; 
     Task<int>.Factory.StartNew(() => 
     { 
      if (num == 3) 
      { 
       Thread.Sleep(20000); 
      } 
      Thread.Sleep(new Random(num).Next(1000, 10000)); 
      Console.WriteLine("Done {0} on {1}", num, Thread.CurrentThread.ManagedThreadId); 
      return num; 
     }).ContinueWith(
     value => 
     { 
      Console.WriteLine("Completed {0} on {1}", value.Result, Thread.CurrentThread.ManagedThreadId); 
     } 
     ,TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    Console.WriteLine("End of Main"); 
    Console.ReadKey(); 
} 
Các vấn đề liên quan