[c#] Task. 매개 변수로 실행 하시겠습니까?

저는 멀티 태스킹 네트워크 프로젝트를 진행하고 있으며 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>Actionvoid

단지 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

한 가지 해결책은 asyncawait. 이것은 기본적으로 SynchronizationContext호출 스레드의 를 캡처하고 호출 후 나머지 메서드에 대한 연속을 await생성하고 생성 된 Task. 이 메서드가 WinForms GUI 스레드에서 실행되는 경우 유형은 WindowsFormsSynchronizationContext.

연속 작업은 캡처 된 항목에 다시 게시 된 후 실행 SynchronizationContext됩니다. 기본적으로 만 다시 실행됩니다. 따라서 await통화 후 시작한 스레드로 돌아갑니다 . 특히를 사용하여 다양한 방법으로이를 변경할 수 있습니다 ConfigureAwait. 즉, 그 방법의 나머지 부분까지 계속되지 않습니다 Task다른 스레드에 완료했습니다. 그러나 호출 스레드는 나머지 메서드가 아닌 병렬로 계속 실행됩니다.

나머지 메소드 실행을 완료하기 위해 기다리는 것은 바람직 할 수도 있고 그렇지 않을 수도 있습니다. 나중에 해당 메서드에서에 전달 된 매개 변수에 액세스하는 것이 없으면 전혀 Task사용하지 않을 수 있습니다 await.

또는 나중에 메서드에서 이러한 매개 변수를 사용할 수도 있습니다. await안전하게 작업을 계속할 수 있으므로 즉시 할 이유가 없습니다 . Task반환 된 값을 변수에 저장할 수 await있고 나중에 같은 방법으로도 저장할 수 있습니다 . 예를 들어, 다른 작업을 한 후 전달 된 매개 변수에 안전하게 액세스해야하는 경우입니다. 다시 말하지만, 당신은 할 수 없습니다 필요 awaitTask당신이 그것을 실행하면 바로.

어쨌든 전달 된 매개 변수와 관련하여이 스레드를 안전하게 만드는 간단한 방법 Task.Run은 다음과 같이하는 것입니다.

먼저 장식한다 RunAsyncasync:

private async void RunAsync()

중요 사항

링크 된 문서에서 언급했듯이 표시된 메서드는 void를 반환 하지 않는 것이 좋습니다 . 이에 대한 일반적인 예외는 버튼 클릭 등과 같은 이벤트 핸들러입니다. 그들은 무효를 반환해야합니다. 그렇지 않으면 항상 또는을 사용할 때 반환하려고합니다 . 몇 가지 이유로 좋은 습관입니다.async TaskTask<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변경 후 비동기 메서드가 실행 되더라도이 코드 줄이 실행되었을 때의 pparam은 무엇이든 갖게됩니다.


답변

지금부터 다음을 수행 할 수도 있습니다.

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));
}