저는 멀티 태스킹 네트워크 프로젝트를 진행하고 있으며 Threading.Tasks
. 나는 간단한 것을 구현했고 Task.Factory.StartNew()
어떻게 할 수 Task.Run()
있을까?
다음은 기본 코드입니다.
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
개체 브라우저System.Threading.Tasks.Task
에서 살펴본 결과 유사한 매개 변수를 찾을 수 없습니다 . 매개 변수 를 취하는 것만 있고 유형 은 없습니다 .Action<T>
Action
void
단지 2 가지가있다는 비슷한 : static Task Run(Action action)
및 static Task Run(Func<Task> function)
수 있지만 모두 없습니다 포스트 매개 변수 (들).
예, 나는 그것을위한 간단한 확장 메서드를 만들 수 있습니다 알고 있지만 내 주요 질문은 우리가 한 줄에 쓸 수 있습니다 와 Task.Run()
?
답변
private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
//Do stuff
}
편집하다
대중적인 요구로 인해 Task
시작된 것이 호출 스레드와 병렬로 실행 된다는 점에 유의해야합니다 . 기본값이라고 가정하면 TaskScheduler
.NET ThreadPool
. 어쨌든 이것은 전달되는 매개 변수 Task
가 잠재적으로 한 번에 여러 스레드에 의해 액세스되어 공유 상태 가되도록 고려해야 함을 의미합니다 . 여기에는 호출 스레드에서 액세스하는 것이 포함됩니다.
위의 코드에서 그 경우는 완전히 논쟁의 여지가 있습니다. 문자열은 변경할 수 없습니다. 그래서 나는 그것들을 예로 사용했습니다. 그러나 당신이 사용하지 않는다고 String
…
한 가지 해결책은 async
및 await
. 이것은 기본적으로 SynchronizationContext
호출 스레드의 를 캡처하고 호출 후 나머지 메서드에 대한 연속을 await
생성하고 생성 된 Task
. 이 메서드가 WinForms GUI 스레드에서 실행되는 경우 유형은 WindowsFormsSynchronizationContext
.
연속 작업은 캡처 된 항목에 다시 게시 된 후 실행 SynchronizationContext
됩니다. 기본적으로 만 다시 실행됩니다. 따라서 await
통화 후 시작한 스레드로 돌아갑니다 . 특히를 사용하여 다양한 방법으로이를 변경할 수 있습니다 ConfigureAwait
. 즉, 그 방법의 나머지 부분까지 계속되지 않습니다 후 가 Task
다른 스레드에 완료했습니다. 그러나 호출 스레드는 나머지 메서드가 아닌 병렬로 계속 실행됩니다.
나머지 메소드 실행을 완료하기 위해 기다리는 것은 바람직 할 수도 있고 그렇지 않을 수도 있습니다. 나중에 해당 메서드에서에 전달 된 매개 변수에 액세스하는 것이 없으면 전혀 Task
사용하지 않을 수 있습니다 await
.
또는 나중에 메서드에서 이러한 매개 변수를 사용할 수도 있습니다. await
안전하게 작업을 계속할 수 있으므로 즉시 할 이유가 없습니다 . Task
반환 된 값을 변수에 저장할 수 await
있고 나중에 같은 방법으로도 저장할 수 있습니다 . 예를 들어, 다른 작업을 한 후 전달 된 매개 변수에 안전하게 액세스해야하는 경우입니다. 다시 말하지만, 당신은 할 수 없습니다 필요 await
온 Task
당신이 그것을 실행하면 바로.
어쨌든 전달 된 매개 변수와 관련하여이 스레드를 안전하게 만드는 간단한 방법 Task.Run
은 다음과 같이하는 것입니다.
먼저 장식한다 RunAsync
과 async
:
private async void RunAsync()
중요 사항
링크 된 문서에서 언급했듯이 표시된 메서드는 void를 반환 하지 않는 것이 좋습니다 . 이에 대한 일반적인 예외는 버튼 클릭 등과 같은 이벤트 핸들러입니다. 그들은 무효를 반환해야합니다. 그렇지 않으면 항상 또는을 사용할 때 반환하려고합니다 . 몇 가지 이유로 좋은 습관입니다.async
Task
Task<TResult>
async
이제 아래와 같이 await
실행할 수 Task
있습니다. await
없이는 사용할 수 없습니다 async
.
await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
따라서 일반적으로 await
작업을 수행하면 매개 변수에 전달 된 매개 변수를 한 번에 여러 스레드에서 수정하는 모든 함정과 함께 잠재적 인 공유 리소스로 취급하는 것을 피할 수 있습니다. 또한 폐쇄에 주의하십시오 . 나는 그것들을 깊이 다루지 않을 것이지만 링크 된 기사는 그것에 대해 훌륭한 일을한다.
사이드 노트
약간의 주제이지만 WinForms GUI 스레드에서으로 표시되어 있으므로 모든 유형의 “차단”을 사용하려면주의해야합니다 [STAThread]
. 사용 await
하면 전혀 차단되지 않지만 일종의 차단과 함께 사용되는 경우가 있습니다.
기술적으로 WinForms GUI 스레드를 차단할 수 없기 때문에 “Block”은 따옴표로 묶 입니다. 예, 당신이 사용하는 경우 lock
윈폼 GUI에이 스레드 합니다 아직도의 “차단”생각 당신에도 불구하고, 메시지를 펌프. 그렇지 않습니다.
이것은 매우 드문 경우에 기괴한 문제를 일으킬 수 있습니다. lock
예를 들어 그림을 그릴 때 를 사용하고 싶지 않은 이유 중 하나입니다 . 그러나 그것은 변덕스럽고 복잡한 경우입니다. 그러나 나는 그것이 미친 문제를 일으키는 것을 보았다. 그래서 완전성을 위해 기록했습니다.
답변
변수 캡처를 사용하여 매개 변수를 “전달”합니다.
var x = rawData;
Task.Run(() =>
{
// Do something with 'x'
});
rawData
직접 사용할 수도 있지만 rawData
작업 외부의 값 (예 : for
루프 의 반복기 )을 변경하면 작업 내부의 값도 변경되므로주의해야합니다.
답변
나는 이것이 오래된 스레드라는 것을 알고 있지만 수락 된 게시물에 여전히 문제가 있기 때문에 사용해야하는 솔루션을 공유하고 싶었습니다.
문제:
Alexandre Severino가 지적했듯이 param
(아래 함수에서) 함수 호출 직후에 변경되면 MethodWithParameter
.
Task.Run(() => MethodWithParameter(param));
내 솔루션 :
이를 설명하기 위해 다음과 같은 코드를 작성했습니다.
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
이를 통해 작업 시작 후 매개 변수가 매우 빠르게 변경 되었음에도 불구하고 (게시 된 솔루션에 문제가 발생 했음) 매개 변수를 비동기 적으로 안전하게 사용할 수있었습니다.
이 접근 방식을 사용하면 param
(값 유형)이 전달 된 값을 가져 오므로 param
변경 후 비동기 메서드가 실행 되더라도이 코드 줄이 실행되었을 때의 p
값 param
은 무엇이든 갖게됩니다.
답변
지금부터 다음을 수행 할 수도 있습니다.
Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
답변
Task.Run을 사용하십시오.
var task = Task.Run(() =>
{
//this will already share scope with rawData, no need to use a placeholder
});
또는 메서드에서 사용하고 나중에 작업을 기다리고 싶다면
public Task<T> SomethingAsync<T>()
{
var task = Task.Run(() =>
{
//presumably do something which takes a few ms here
//this will share scope with any passed parameters in the method
return default(T);
});
return task;
}
답변
원래 문제가 내가 가진 것과 동일한 문제인지는 확실하지 않습니다. 반복기의 값을 유지하면서 작업자 함수에 수많은 변수를 전달하지 않도록 인라인을 유지하면서 루프 내부의 계산에서 최대 CPU 스레드를 원합니다.
for (int i = 0; i < 300; i++)
{
Task.Run(() => {
var x = ComputeStuff(datavector, i); // value of i was incorrect
var y = ComputeMoreStuff(x);
// ...
});
}
나는 외부 반복자를 변경하고 게이트로 그 값을 지역화함으로써 이것을 작동시켰다.
for (int ii = 0; ii < 300; ii++)
{
System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1);
Task.Run(() => {
int i = ii;
handoff.Signal();
var x = ComputeStuff(datavector, i);
var y = ComputeMoreStuff(x);
// ...
});
handoff.Wait();
}
답변
아이디어는 위와 같은 신호 사용을 피하는 것입니다. int 값을 구조체로 펌핑하면 해당 값이 구조체에서 변경되지 않습니다. 나는 다음과 같은 문제가 있었다. 루프 var 나는 DoSomething (i)가 호출되기 전에 변경 될 것이다 (나는 루프의 끝에서 () => DoSomething (i, i i)가 호출 되기 전에 증가했다 ). 구조체를 사용하면 더 이상 발생하지 않습니다. 찾아야 할 불쾌한 버그 : DoSomething (i, i i)은 멋져 보이지만 i에 대해 다른 값으로 매번 호출되는지 (또는 i = 100으로 100 번만) 호출되는지 확실하지 않으므로-> struct
struct Job { public int P1; public int P2; }
…
for (int i = 0; i < 100; i++) {
var job = new Job { P1 = i, P2 = i * i}; // structs immutable...
Task.Run(() => DoSomething(job));
}