2014-05-08 19 views
9

Tôi có một phương thức không đồng bộ RequestInternalAsync() yêu cầu một tài nguyên bên ngoài và muốn viết một phương thức bao bọc. giảm song song.Giới hạn song song của một phương thức Async và không chặn chủ đề Thread-Pool

Tùy chọn đầu tiên, mà nói đến cái tâm là TaskScheduler với đồng thời giới hạn (LimitedConcurrencyLevelTaskScheduler, ConcurrentExclusiveSchedulerPair v.v.).

Nhưng để chạy tác vụ với trình lên lịch tùy chỉnh, tôi phải bắt đầu tác vụ bằng cách sử dụng TaskFactory chỉ chấp nhận Action<>, tức là tôi không thể làm điều đó bằng cách không chặn thêm chuỗi chỉ để chờ thực hiện phương thức bên trong.

Tùy chọn thứ hai là SemaphoreSlim, nó thực hiện công việc của mình, nhưng trong trường hợp này tôi đang thực hiện điều chỉnh chính mình, thay vì sử dụng TaskScheduler.

static void Main(string[] args) 
{ 
    // TESTING 1 

    var task1 = Task.WhenAll(Enumerable.Range(1, 10).Select(i => RequestAsyncBad())); 

    task1.Wait(); 

    // TESTING 2 

    var task2 = Task.WhenAll(Enumerable.Range(1, 10).Select(i => RequestAsyncBetter())); 

    task2.Wait(); 
} 

private static Task RequestInternalAsync() 
{ 
    return Task.Delay(500); 
} 

Giải pháp # 1:

private static readonly ConcurrentExclusiveSchedulerPair _concurrentPair 
    = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 2); 

public static Task RequestAsyncBad() 
{ 
    // Dumb: Because TaskFactory doesn't provide an overload which accepts another task, only action. 
    // As result, we blocking a thread to just wait until the inner task finishes. 

    return Task.Factory.StartNew(() => RequestInternalAsync().Wait(), 
     CancellationToken.None, TaskCreationOptions.DenyChildAttach, _concurrentPair.ConcurrentScheduler); 
} 

Giải pháp # 2 (tốt hơn):

private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2); 

public static async Task RequestAsyncBetter() 
{ 
    // Here we don't waste thread-pool thread on waiting for a completion of inner task, 
    // but instead of using TaskScheduler, implementing a hand-made stuff with semaphore. 

    await _semaphore.WaitAsync().ConfigureAwait(false); 

    try 
    { 
     await RequestInternalAsync(); 
    } 
    finally 
    { 
     _semaphore.Release(); 
    } 
} 

cách thanh lịch hơn để làm điều này là gì?

  • để tái sử dụng tiêu chuẩn Task API của TPL và TaskScheduler
  • và không chặn một chủ đề phụ

Trả lời

8

TaskScheduler chỉ hữu ích cho công việc liên kết CPU. Công việc của bạn không sử dụng chủ đề. Nó sử dụng cổng hoàn thành IO có nghĩa là cuộc gọi mạng của bạn không giữ bất kỳ chủ đề nào cả. Không có cách nào để liên quan đến một TaskScheduler cho một hoạt động IO.

Nếu bạn chưa được thuyết phục: Không đồng bộ IO trong .NET dựa trên việc sử dụng TaskCompletionSource không bị ràng buộc vào chủ đề hoặc trình lập lịch theo cách nhỏ nhất.

SemaphoreSlim là cách phù hợp để thực hiện. Hoặc, tạo một ServicePoint và đặt đồng thời tối đa của nó. Chỉ áp dụng cho các yêu cầu HTTP.

Lưu ý rằng nếu bạn thấy mình đang sử dụng Wait bạn nên ngần ngại và suy nghĩ về những gì bạn đang làm. Thông thường, đây là một sai lầm.

+0

Cảm ơn! Vì vậy, như tôi hiểu, 'TaskScheduler' s chỉ chịu trách nhiệm * WHEN * và * WHERE * để bắt đầu một nhiệm vụ, nhưng không được thiết kế để kiểm soát thời gian sống của nhiệm vụ bắt đầu? –

+2

Tôi sẽ không nói điều đó. Lưu ý, có hai loại nhiệm vụ (và điều này là khó hiểu!): CPU-công việc (một nhiệm vụ được hỗ trợ bởi một đại biểu mà cuối cùng chạy) và "công việc khác" được hỗ trợ bởi TaskCompletionSource. Chỉ trường hợp đầu tiên sử dụng TaskScheduler (và nó luôn luôn sử dụng một). Vì vậy, tôi muốn nói rằng một TaskScheduler có trách nhiệm hoàn thành một nhiệm vụ được hỗ trợ bởi một đại biểu bằng cách chạy các đại biểu khi nào và nơi nó thấy phù hợp. – usr

+0

Không thể cung cấp cho TCS một bộ lập lịch không phải TPL đã từng yêu cầu một người thực hiện một nhiệm vụ dựa trên TCS .; Tôi tin rằng sự hiểu biết điểm này là khá sáng suốt khi làm việc với TPL. – usr

0
public static async Task RequestAsyncCool() 
{ 
    await Task.Factory.StartNew(async() => { 
      await RequestInternalAsync(); 
     }, 
     CancellationToken.None, 
     TaskCreationOptions.DenyChildAttach, 
     TaskScheduler.Current); 
} 

Bạn thực sự không nên Wait cho Tasks. Xem https://www.google.com/search?q=task+wait+deadlock

Bạn có nhìn vào TPL DataFlow không? Nó có thể chỉ là điều dành cho bạn ...

+0

http://blogs.microsoft.co.il/bnaya/2011/08/02/tpl-dataflow-part-1/ – spender

+0

Vấn đề ở đây là nếu tôi đặt một phương thức không đồng bộ vào 'Task.Factory.StartNew' chấp nhận 'Func ', và giới hạn đồng thời bằng cách chỉ định một 'TaskScheduler' tùy chỉnh, thì giới hạn đồng thời sẽ chỉ làm việc cho phần đầu tiên của các phương thức async của tôi, nó sẽ không đợi cho đến khi 2 phương thức đầu tiên kết thúc trước khi bắt đầu phương thức mới (2 là giới hạn song song của tôi). Đó là lý do tại sao tôi phải đặt 'Wait()' ở đó, và đó là lý do tại sao tôi không thích điều này và hỏi câu hỏi này :) –

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