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ụ
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? –
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
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