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


87

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


매개 변수 의 이 무엇인지는 명확하지 않습니다 . 어디에서 왔을까요? 이미 가지고 있다면 람다 식으로 캡처하십시오 ...
Jon Skeet

@JonSkeet rawData은 컨테이너 클래스 (예 : DataPacket)가있는 네트워크 데이터 패킷이며 GC 압력을 줄이기 위해이 인스턴스를 재사용하고 있습니다. 따라서에서 rawData직접 사용하면 처리 Task하기 전에 (아마도) 변경할 Task수 있습니다. 이제 다른 byte[]인스턴스를 만들 수 있다고 생각 합니다. 저에게 가장 간단한 해결책이라고 생각합니다.
MFatihMAR

예, 바이트 배열을 복제해야하는 경우 바이트 배열을 복제합니다. 가지고 Action<byte[]>있다고해서 바뀌지 않습니다.
Jon Skeet

다음 은 작업에 매개 변수를 전달 하는 몇 가지 좋은 솔루션 입니다.
Just Shadow

답변:


116
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예를 들어 그림을 그릴 때 를 사용하고 싶지 않은 이유 중 하나입니다 . 그러나 그것은 변덕스럽고 복잡한 경우입니다. 그러나 나는 그것이 미친 문제를 일으키는 것을 보았다. 그래서 완전성을 위해 기록했습니다.


21
당신은 기다리고 있지 않습니다 Task.Run(() => MethodWithParameter(param));. 즉 , 이후param수정 되면 . Task.RunMethodWithParameter
Alexandre Severino

7
이것이 틀렸을 때 왜 받아 들여지는 대답인가. 상태 객체를 전달하는 것과 전혀 동일하지 않습니다.
Egor Pavlikhin

6
@ Zer0 상태 개체는 Task.Factory.StartNew msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx 의 두 번째 매개 변수이며 현재 개체의 값을 저장합니다. StartNew를 호출하면 응답이 클로저를 생성하여 참조를 유지하므로 (작업이 실행되기 전에 param 값이 변경되면 작업에서도 변경됨) 코드가 질문이 요청한 것과 전혀 동일하지 않습니다. . 정답은 Task.Run ()으로 작성할 방법이 없다는 것입니다.
Egor Pavlikhin

2
Zer0 @ (귀하의 링크 당 Task.Run과 동일하지 않습니다) 2 매개 변수를 폐쇄하고 Task.Factory.StartNew와 구조체 Task.Run위한 것이다 후자의 경우에 사본이 만들어되기 때문에 다르게 동작합니다. 내 실수는 원래 주석에서 일반적으로 객체를 언급하는 것이 었는데, 내가 의미하는 바는 이들이 완전히 동등하지 않다는 것입니다.
Egor Pavlikhin

3
Toub의 기사를 읽으면 "객체 상태를 허용하는 오버로드를 사용하게되며, 성능에 민감한 코드 경로에 대해 클로저 및 해당 할당을 피하는 데 사용할 수 있습니다."라는 문장을 강조합니다. 나는 이것이 @Zero가 Task.Run over StartNew 사용을 고려할 때 의미하는 바라고 생각합니다.
davidcarr

35

변수 캡처를 사용하여 매개 변수를 "전달"합니다.

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

rawData직접 사용할 수도 있지만 rawData작업 외부의 값 (예 : for루프 의 반복기 )을 변경하면 작업 내부의 값도 변경되므로주의해야합니다.


11
+1을 호출 한 직후 변수가 변경 될 수 있다는 중요한 사실을 고려합니다 Task.Run.
Alexandre Severino

1
이것이 어떻게 도움이 될까요? 작업 스레드 내에서 x를 사용하고 x는 개체에 대한 참조이며 작업 스레드가 실행 중일 때 개체가 동시에 수정되면 혼란을 일으킬 수 있습니다.
Ovi

1
@ Ovi-WanKenobi 예,하지만이 질문에 대한 내용은 아닙니다. 매개 변수를 전달하는 방법이었습니다. 객체에 대한 참조를 일반 함수에 대한 매개 변수로 전달하면 거기에서도 똑같은 문제가 발생합니다.
스콧의 전관

네, 작동하지 않습니다. 내 작업에는 호출 스레드에서 x에 대한 참조가 없습니다. 난 그냥 null을 얻습니다.
David Price

7

나는 이것이 오래된 스레드라는 것을 알고 있지만 수락 된 게시물에 여전히 문제가 있기 때문에 사용해야하는 솔루션을 공유하고 싶었습니다.

문제:

Alexandre Severino가 지적했듯이 param(아래 함수에서) 함수 호출 직후에 변경되면 MethodWithParameter.

Task.Run(() => MethodWithParameter(param)); 

내 솔루션 :

이를 설명하기 위해 다음과 같은 코드를 작성했습니다.

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

이를 통해 작업 시작 후 매개 변수가 매우 빠르게 변경 되었음에도 불구하고 (게시 된 솔루션에 문제가 발생 했음) 매개 변수를 비동기 적으로 안전하게 사용할 수있었습니다.

이 접근 방식을 사용하면 param(값 유형)이 전달 된 값을 가져 오므로 param변경 후 비동기 메서드가 실행 되더라도이 코드 줄이 실행되었을 때의 pparam은 무엇이든 갖게됩니다.


5
적은 오버 헤드로 더 읽기 쉽게 할 수있는 방법을 생각할 수있는 모든 사람을 간절히 기다리고 있습니다. 이것은 분명히 다소 추한 것입니다.
Kaden Burgart 2016 년

5
여기 있습니다 :var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Stephen Cleary

1
그건 그렇고, Stephen은 이미 1 년 반 전에 그의 대답에서 논의했습니다.
Servy

1
@Servy : 실제로 Scott의 대답 이었습니다 . 나는 이것에 대답하지 않았다.
Stephen Cleary

Scott의 대답은 for 루프에서 이것을 실행했기 때문에 실제로 저에게 효과가 없었을 것입니다. 로컬 매개 변수는 다음 반복에서 재설정되었을 것입니다. 내가 게시 한 답변의 차이점은 매개 변수가 람다 식의 범위로 복사되므로 변수가 즉시 안전하다는 것입니다. Scott의 대답에서 매개 변수는 여전히 동일한 범위에 있으므로 회선 호출과 비동기 함수 실행 사이에서 여전히 변경 될 수 있습니다.
Kaden Burgart

6

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

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

5

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

1
그렇게 하면 OP의 StartNew 예제 에서처럼 전달 된 것과 for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }동일하게 작동하지 않을 경우 클로저에주의 rawData하십시오.
Scott Chamberlain

@ScottChamberlain-다른 예처럼 보입니다;) 대부분의 사람들이 람다 값을 닫는 것에 대해 이해하기를 바랍니다.
Travis J

3
그리고 그 이전 코멘트 말도하지 경우, 주제에 에릭 리퍼의 블로그를 참조하십시오 blogs.msdn.com/b/ericlippert/archive/2009/11/12/... 이 잘 발생하는 이유는 설명한다.
Travis J

2

원래 문제가 내가 가진 것과 동일한 문제인지는 확실하지 않습니다. 반복기의 값을 유지하면서 작업자 함수에 수많은 변수를 전달하지 않도록 인라인을 유지하면서 루프 내부의 계산에서 최대 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();
}

0

아이디어는 위와 같은 신호 사용을 피하는 것입니다. 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));
}

1
질문에 답할 수 있지만 검토를 위해 플래그가 지정되었습니다. 설명이없는 답변은 종종 품질이 낮은 것으로 간주됩니다. 이것이 정답 인 이유에 대한 설명을 제공하십시오.
Dan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.