Task.Factory.StartNew ()는 호출 스레드가 아닌 다른 스레드를 사용하도록 보장됩니까?
함수에서 새 작업을 시작하고 있지만 동일한 스레드에서 실행되는 것을 원하지 않습니다. 나는 그것이 다른 스레드라면 어떤 스레드에서 실행되는지 상관하지 않습니다 (따라서이 질문에 제공된 정보 는 도움 이 되지 않습니다).
다시 입력 TestLock
하기 전에 아래 코드가 항상 종료된다는 것을 보장 Task t
합니까? 그렇지 않은 경우 재진입을 방지하기 위해 권장되는 디자인 패턴은 무엇입니까?
object TestLock = new object();
public void Test(bool stop = false) {
Task t;
lock (this.TestLock) {
if (stop) return;
t = Task.Factory.StartNew(() => { this.Test(stop: true); });
}
t.Wait();
}
편집 : Jon Skeet 및 Stephen Toub의 아래 답변에 따라 재진입을 결정적으로 방지하는 간단한 방법은이 확장 메서드에 설명 된대로 CancellationToken을 전달하는 것입니다.
public static Task StartNewOnDifferentThread(this TaskFactory taskFactory, Action action)
{
return taskFactory.StartNew(action: action, cancellationToken: new CancellationToken());
}
이 질문에 대해 PFX 팀원 인 Stephen Toub에게 메일을 보냈습니다 . 그는 많은 세부 사항을 포함하여 정말 빨리 제게 돌아 왔습니다. 그래서 여기에 그의 텍스트를 복사하여 붙여 넣겠습니다. 많은 양의 인용 된 텍스트를 읽는 것이 바닐라 흑백보다 덜 편안해지기 때문에 모든 것을 인용하지는 않았지만 실제로 이것은 Stephen입니다-나는이 정도의 내용을 모릅니다 :) 내가 만든 이 답변 커뮤니티 위키는 아래의 모든 장점이 실제로 내 콘텐츠가 아니라는 것을 반영합니다.
당신이 호출하면
Wait()
A의 작업 완료있어, 어떤 (가 작업이 완료 경우 만 예외를 던질거야 차단이되지 않습니다 TaskStatus 이외의RanToCompletion
, 또는 그렇지 않으면로 돌아 NOP ).Wait()
이미 실행중인 작업을 호출 하는 경우 합리적으로 수행 할 수있는 다른 작업이 없으므로 차단해야합니다 (블록이라고 말하면 일반적으로 두 가지의 혼합을 수행하므로 실제 커널 기반 대기 및 회전을 모두 포함합니다. ). 마찬가지로 또는 상태Wait()
가있는 작업을 호출 하면 작업이 완료 될 때까지 차단됩니다. 그중 어느 것도 논의되고있는 흥미로운 사례가 아닙니다.Created
WaitingForActivation
흥미로운 경우는 상태
Wait()
에서 Task 를 호출 할 때입니다WaitingToRun
. 즉, 이전에 TaskScheduler 에 대기열에 추가 되었지만 TaskScheduler가 아직 실제로 Task의 델리게이트를 실행하지 않은 상태입니다. 이 경우에 대한 호출Wait
은 스케줄러의TryExecuteTaskInline
메서드에 대한 호출을 통해 현재 스레드에서 작업을 실행할 수 있는지 여부를 스케줄러에게 묻습니다 . 이를 인라인 이라고 합니다. 스케줄러는에 대한 호출을 통해 작업을 인라인base.TryExecuteTask
하거나 'false'를 반환하여 작업을 실행하고 있지 않음을 나타낼 수 있습니다 (종종 이것은 다음과 같은 논리로 수행됩니다.return SomeSchedulerSpecificCondition() ? false : TryExecuteTask(task);
TryExecuteTask
부울을 반환하는 이유 는 주어진 태스크가 한 번만 실행되도록 동기화를 처리하기 때문입니다. 따라서 스케줄러가에서 Task의 인라인을 완전히 금지하려면 다음Wait
과 같이 구현할 수 있습니다return false;
. 스케줄러가 가능할 때마다 항상 인라인을 허용하려면 다음과 같이 구현하면됩니다.return TryExecuteTask(task);
현재 구현 (.NET 4 및 .NET 4.5 모두 변경 될 것으로 예상하지 않음)에서 ThreadPool을 대상으로하는 기본 스케줄러는 현재 스레드가 ThreadPool 스레드이고 해당 스레드가 해당 스레드 인 경우 인라인을 허용합니다. 하나는 이전에 작업을 대기열에 넣었습니다.
여기에는 임의의 재진입이 없습니다. 기본 스케줄러가 작업을 기다릴 때 임의의 스레드를 펌핑하지 않는다는 점에 유의하십시오. 작업을 인라인 할 수만 허용하고 해당 작업을 인라인하는 모든 작업이 결정합니다. 할 것. 또한
Wait
특정 조건에서 스케줄러에게 묻지 않고 대신 차단하는 것을 선호합니다. 예를 들어 취소 가능한 CancellationToken 을 전달하거나 무한이 아닌 제한 시간을 전달하는 경우 작업 실행을 인라인하는 데 임의의 시간이 걸릴 수 있으므로 인라인을 시도하지 않습니다. , 취소 요청 또는 시간 초과가 크게 지연 될 수 있습니다. 전반적으로 TPL은 작업을 수행하는 스레드를 낭비하는 것 사이에서 적절한 균형을 유지하려고합니다.Wait
스레드를 너무 많이 사용하고 재사용합니다. 이러한 종류의 인라인은 여러 작업을 생성 한 다음 모든 작업이 완료 될 때까지 기다리는 재귀 적 분할 및 정복 문제 (예 : QuickSort )에 정말 중요합니다 . 인라이닝없이 이러한 작업을 수행하면 풀의 모든 스레드와 제공하고자하는 향후 스레드가 모두 소모되므로 매우 빠르게 교착 상태가됩니다.와는 별개로
Wait
, Task.Factory.StartNew 호출이 작업을 끝낼 수 있으며, 사용중인 스케줄러가 QueueTask 호출의 일부로 작업을 동 기적으로 실행하도록 선택한 경우 에도 (원격으로) 가능합니다 . .NET에 내장 된 스케줄러 중 어느 것도이 작업을 수행하지 않습니다. 개인적으로 이것이 스케줄러에 좋지 않은 설계라고 생각하지만 이론적으로는 다음과 같이 가능합니다.protected override void QueueTask(Task task, bool wasPreviouslyQueued) { return TryExecuteTask(task); }
그 오버로드는
Task.Factory.StartNew
a를 받아들이지 않습니다 . targets 의 경우에서TaskScheduler
스케줄러를 사용합니다 . 즉 , 이 신화에 대기열에있는 Task 내에서 호출하면에 대기열 에 추가되어 호출이 Task를 동 기적으로 실행하게됩니다. 당신이 (예를 들어 당신이 라이브러리를 구현하고 당신은 당신이 호출 할 수 어디로 가는지 모르는)이 염려 전혀 있다면, 당신은 명시 적으로 전달할 수 있습니다 받는 전화 사용 (항상로 이동하는 ) 또는 생성 된를 사용하여 .TaskFactory
Task.Factory
TaskScheduler.Current
Task.Factory.StartNew
RunSynchronouslyTaskScheduler
RunSynchronouslyTaskScheduler
StartNew
TaskScheduler.Default
StartNew
Task.Run
TaskScheduler.Default
TaskFactory
TaskScheduler.Default
편집 : 좋아, 내가 완전히 잘못된 것처럼 보이며 현재 작업을 기다리고있는 스레드 가 하이재킹 될 수 있습니다. 다음은 이러한 일이 발생하는 간단한 예입니다.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
static void Main() {
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(Launch).Wait();
}
}
static void Launch()
{
Console.WriteLine("Launch thread: {0}",
Thread.CurrentThread.ManagedThreadId);
Task.Factory.StartNew(Nested).Wait();
}
static void Nested()
{
Console.WriteLine("Nested thread: {0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
샘플 출력 :
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 3
Nested thread: 3
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
Launch thread: 4
Nested thread: 4
보시다시피, 대기중인 스레드가 새 작업을 실행하기 위해 재사용되는 경우가 많습니다. 이는 스레드가 잠금을 획득 한 경우에도 발생할 수 있습니다. 불쾌한 재진입. 나는 적당히 충격을 받고 걱정된다 :(
왜 그런 일이 일어나지 않도록 뒤로 구부리기보다는 그것을 위해 디자인하지 않는가?
TPL은 여기에서 붉은 청어입니다. 사이클을 생성 할 수 있다면 모든 코드에서 재진입이 발생할 수 있으며 스택 프레임의 '남쪽'에서 무슨 일이 일어날 지 확신 할 수 없습니다. 동기식 재진입이 여기에서 가장 좋은 결과입니다. 최소한 스스로 교착 상태를 유지할 수는 없습니다 (쉽게).
Locks manage cross thread synchronisation. They are orthogonal to managing reentrancy. Unless you are protecting a genuine single use resource (probably a physical device, in which case you should probably use a queue), why not just ensure your instance state is consistent so reentrancy can 'just work'.
(Side thought: are Semaphores reentrant without decrementing?)
You could easily test this by writting a quick app that shared a socket connection between threads / tasks.
The task would acquire a lock before sending a message down the socket and waiting for a response. Once this blocks and becomes idle (IOBlock) set another task in the same block to do the same. It should block on acquiring the lock, if it does not and the second task is allowed to pass the lock because it run by the same thread then you have an problem.
Solution with new CancellationToken()
proposed by Erwin did not work for me, inlining happened to occur anyway.
So I ended up using another condition advised by Jon and Stephen (... or if you pass in a non-infinite timeout ...
):
Task<TResult> task = Task.Run(func);
task.Wait(TimeSpan.FromHours(1)); // Whatever is enough for task to start
return task.Result;
Note: Omitting exception handling etc here for simplicity, you should mind those in production code.
'program story' 카테고리의 다른 글
동일한 서비스에서 AngularJS 서비스의 함수를 호출합니까? (0) | 2020.10.22 |
---|---|
Requirejs를 언제 사용하고 번들 자바 스크립트를 언제 사용해야합니까? (0) | 2020.10.22 |
Windows에서 동시에 실행되는 여러 Java 버전 (0) | 2020.10.22 |
커스텀 Python 목록 정렬 (0) | 2020.10.21 |
60 초 내에 안정적인 Firefox 연결을 얻을 수 없음 (127.0.0.1:7055) (0) | 2020.10.21 |