21

UI 스레드 메시지 루프를 앞으로 반복 할 때까지 코드 실행을 연기해야한다면 다음과 같이 할 수 있습니다.

await Task.Factory.StartNew(
    () => {
        MessageBox.Show("Hello!");
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext());

이것은await Task.Yield(); MessageBox.Show("Hello!");, 내가 원하는 경우 작업을 취소 할 수있는 옵션이 있습니다.

기본 동기화 컨텍스트가있는 경우 비슷한 방법으로await Task.Run풀 스레드에서 계속하려면.

사실, 나는 좋아한다.Task.Factory.StartNewTask.Run이상Task.Yield두 코드 모두 연속 코드의 범위를 명시 적으로 정의하기 때문입니다.

그래서 어떤 상황에서await Task.Yield()실제로 유용합니까?


  • 나만 사용 했어.Task.Yield단위 테스트 및모호한 ASP.NET 문제를 해결하려면async방법~해서는 안된다.동 기적으로 완료하다. - Stephen Cleary
  • 관련 항목 :Task.Yield - 실제 사용법? - noseratio
  • 귀하의 질문에이 질문을하지 않았지만 이해 관계가 있습니다. 부름MessageBox.Show()지나치지 않고IWin32Window owner논의창에 포커스가 없을 때 해당 코드가 실행되면 메시지 상자가 "아래에 표시됩니다"라는 메시지 상자가 나타날 수 있습니다. 이것은 GUI 스레드에서 수행하는 경우 특히 혼란 스럽습니다. 또한, 통과하면IWin32WindowMessageBox.Show()UI 스레드에서 그렇게해야합니다. 그래서, 그 경우, 당신은~해서는 안된다.용도Task.Run()절대로 필요한 것패스TaskSchedulerStartNew(). - binki
  • 또한, 넣을 필요가 없습니다.MessageBox.Show()왜냐하면MessageBox.Show()메시지 대기열을 펌핑하고 GUI에 예정된 연속성을 실행합니다.SynchronizationContext. 즉, 양식을 계속 업데이트하고 사물을 표시 할 수 있습니다.async당신이 평소처럼MessageBox.Show()보여주고있다. - binki

4 답변


1

한 가지 상황Task.Yield()실제로 유용 할 때가있다.await재귀 적으로 동기식으로 완료된Task에스. csharp의async/await "Zalgo 출시"가능한 경우 연속성을 동 기적으로 실행하여 완전 동기식 재귀 시나리오의 스택이 프로세스가 종료 될만큼 충분히 커질 수 있습니다. 나는 이것이 또한 부분적으로 꼬리 호출 때문에 지원 될 수없는 것 같아요.Task우회.await Task.Yield()인라인이 아닌 스케줄러가 계속 실행하도록 예약하므로 스택의 증가를 피하고이 문제를 해결할 수 있습니다.

또한,Task.Yield()메서드의 동기 부분을 짧게하는 데 사용할 수 있습니다. 호출자가 메소드를 수신해야하는 경우Task메소드가 어떤 동작을 수행하기 전에,Task.Yield()강제로 반환Task그렇지 않으면 자연스럽게 일어날 것입니다. 예를 들어, 다음 로컬 메소드 시나리오에서async메서드는 자체에 대한 참조를 얻을 수 있습니다.Task안전하게 (단 하나 동시성에 이것을 달리고 있다고 가정하십시오SynchronizationContextwinforms 또는 via와 같은니트로AsyncContext.Run()) :

using Nito.AsyncEx;
using System;
using System.Threading.Tasks;

class Program
{
    // Use a single-threaded SynchronizationContext similar to winforms/WPF
    static void Main(string[] args) => AsyncContext.Run(() => RunAsync());

    static async Task RunAsync()
    {
        Task<Task> task = null;
        task = getOwnTaskAsync();
        var foundTask = await task;
        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

        async Task<Task> getOwnTaskAsync()
        {
            // Cause this method to return and let the 「task」 local be assigned.
            await Task.Yield();
            return task;
        }
    }
}

산출:

3 == 3: True

나는 실제 상황을 생각할 수 없다는 점에 유감스럽게 생각한다.async방법은 뭔가를하는 가장 좋은 방법입니다. 내가 보여준 것처럼 당신이 트릭을 할 수 있다는 것을 알 때가끔 유용 할 수 있지만, 너무 위험한 경향이 있습니다. 종종 더 잘 읽고 더 읽기 쉬운 방법으로 데이터를 전달할 수 있습니다. 예를 들어 로컬 메서드에 자체 참조를 전달할 수 있습니다.Task~을 사용하여TaskCompletionSource대신 :

using System;
using System.Threading.Tasks;

class Program
{
    // Fully free-threaded! Works in more environments!
    static void Main(string[] args) => RunAsync().Wait();

    static async Task RunAsync()
    {
        var ownTaskSource = new TaskCompletionSource<Task>();
        var task = getOwnTaskAsync(ownTaskSource.Task);
        ownTaskSource.SetResult(task);
        var foundTask = await task;
        Console.WriteLine($"{task?.Id} == {foundTask?.Id}: {task == foundTask}");

        async Task<Task> getOwnTaskAsync(
            Task<Task> ownTaskTask)
        {
            // This might be clearer.
            return await ownTaskTask;
        }
    }
}

산출:

2 == 2: True


  • 재귀를 처리하는 것은 실제로 비용이 많이 들지만 특정 동기화 컨텍스트에 따라 달라 지지만 실제로 사용하기에 좋습니다. - noseratio
  • @noseratio True. 오버 헤드가 있다는 사실은 카운터를 통과시키고 매 30 회마다 재귀를 사용하여 약간 완화 할 수 있습니다. 내가 무엇인지 잘 모르겠다.SynchronizationContext그래도 그것과 관련이있다. 이런 식으로하면 그래픽 응용 프로그램에서 GUI 스레드가 아닌 것 같습니다. 그러나 Task API를 사용하여 반복적으로 작업하는 경우 기다려야 할 작업이 완료되지 않았다고 확신 할 수 없으므로 수행해야 할 수도 있습니다. 당신은 언제나 그것을 태스크에 조건부로 만들 수 있습니다.Task.IsCompleted및 카운터. - binki

6

비동기 태스크가 값을 리턴하도록하려는 경우를 고려하십시오.

기존 동기 방식 :

public int DoSomething()
{
    return SomeMethodThatReturnsAnInt();
}

비동기를 만들려면 비동기 키워드를 추가하고 반환 유형을 변경하십시오.

public async Task<int> DoSomething()

Task.Factory.StartNew ()를 사용하려면 메서드의 한 줄 본문을 다음과 같이 변경하십시오.

// start new task
var task = Task<int>.Factory.StartNew(
    () => {
        return SomeMethodThatReturnsAnInt();
    },
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext() );

// await task, return control to calling method
await task;

// return task result
return task.Result;

사용하는 경우 한 줄 추가 vs.await Task.Yield()

// this returns control to the calling method
await Task.Yield();

// otherwise synchronous method scheduled for async execution by the 
// TaskScheduler of the calling thread
return SomeMethodThatReturnsAnInt();

후자는 훨씬 간결하고 읽기 쉽고 실제로 기존 방법을 많이 변경하지 않습니다.


  • 공평하게 말하면 여전히 한 줄입니다.return await Task.Factory.StartNew(() => SomeMethodThatReturnsAnInt(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());. 그럼에도 불구하고 나는 +1 점을 본다. 이 문제는 제어 흐름을 다소 비 직관적으로 변경하는 것으로 논의됩니다.이리. - noseratio
  • 음, 당신은 또한await task;선 - Moho
  • 이걸 다시 방문하십시오.SomeMethodThatReturnsAnInt으로async간단히 할 수 있습니다.public Task<int> DoSomething() { return Task.FromResult(SomeMethodThatReturnsAnInt()); }. 또는,async예외 전파의 의미:public async Task<int> DoSomething() { return await Task.FromResult(SomeMethodThatReturnsAnInt()); }. 분명히,await Task.Yield()여기에 중복되고 바람직하지 않습니다. 내 표를 취소하는 것에 대해 죄송합니다. - noseratio
  • 더 나은 방법입니다.단순히 CS1998 경고를 억제하십시오.컴파일러 경고를 간접적으로 억제하기 위해 컨텍스트 스위치를 강제 실행하는 것보다. - binki

4

Task.Yield()그렇지 않으면 동기식 부분에서 "펀칭 구멍"에 적합합니다.async방법.

개인적으로 내가 스스로 취소 한 경우에 유용하다는 것을 알았습니다.async방법 (자체적으로CancellationTokenSource매우 짧은 기간 (즉, 상호 의존적 인 UI 요소의 이벤트 핸들러)에 여러 번 호출 할 수있는 이전에 생성 된 인스턴스를 취소합니다. 이러한 상황에서Task.Yield()다음에IsCancellationRequested빨리 확인하십시오.CancellationTokenSource어쨌든 결과가 폐기 될 잠재적으로 값 비싼 작업을 수행하는 것을 막을 수 있습니다.

다음은 SelfCancellingAsync에 마지막으로 대기중인 호출 만 비싼 작업을 수행하고 완료까지 실행되는 예제입니다.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskYieldExample
{
    class Program
    {
        private static CancellationTokenSource CancellationTokenSource;

        static void Main(string[] args)
        {
            SelfCancellingAsync();
            SelfCancellingAsync();
            SelfCancellingAsync();

            Console.ReadLine();
        }

        private static async void SelfCancellingAsync()
        {
            Console.WriteLine("SelfCancellingAsync starting.");

            var cts = new CancellationTokenSource();
            var oldCts = Interlocked.Exchange(ref CancellationTokenSource, cts);

            if (oldCts != null)
            {
                oldCts.Cancel();
            }

            // Allow quick cancellation.
            await Task.Yield();

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do the "meaty" work.
            Console.WriteLine("Performing intensive work.");

            var answer = await Task
                .Delay(TimeSpan.FromSeconds(1))
                .ContinueWith(_ => 42, TaskContinuationOptions.ExecuteSynchronously);

            if (cts.IsCancellationRequested)
            {
                return;
            }

            // Do something with the result.
            Console.WriteLine("SelfCancellingAsync completed. Answer: {0}.", answer);
        }
    }
}

여기서 목표는 동일한 코드에서 동 기적으로 실행되는 코드를 허용하는 것입니다.SynchronizationContext비동기 메소드에 대한 대망 호출이 반환 된 직후 (첫 번째 호출시await)를 사용하여 async 메서드의 실행에 영향을주는 상태를 변경합니다. 이것은 많은 사람들이 성취 한 것과 비슷합니다.Task.Delay(나는 여기서 0이 아닌 지연 기간을 말하고있다.) 그러나실제의, 잠재적으로 눈에 띄는 지연, 어떤 경우에는 반갑지 않을 수 있습니다.


  • 귀하의 의견을 보내 주셔서 감사합니다.Task.Yield또는 도우미Task.Delay,여기에 내 버전이있다.취소 및 다시 시작 패턴. - noseratio
  • Btw,   " 고기 같은 " 예외를 처리하는 데 문제가있을 수 있습니다. ~ 때문에 일하다.async void서명SelfCancellingAsync. - noseratio
  • 나는 구체적이다.아니간결을 기하기 위해 예외를 처리하는 것은 패턴을 사용하도록 선택한 사람의 작업입니다. 예제는void왜냐하면Task기다려야한다고 제안합니다. 여기서는 그렇지 않습니다. 또한 예외를 throw하는 것과는 대조적으로 취소가 감지 될 때 메소드에서 복귀하는 이유 (일반적으로 호출자와의 통신 취소에 대해 선호하는 방식 임)입니다. - Kirill Shlenskiy
  • 맞습니다. 두 가지 유형의 비동기 메소드간에 주요 차이점이 있습니다. 네가 가질 때마다async Task예외를 던지고 호출자가 처리하도록하는 것이 바람직한 방법입니다. 의 경우async void그러나 예외 처리는 일반적으로 async 메서드 자체의 본문에 연결해야합니다. - Kirill Shlenskiy
  • 귀하의 작업이 완전히 격리되어 있고 처리 할 수 있다면 좋을 것 같습니다.모든내부 예외SelfCancellingAsync. 그러나 결국 외부에서 취소를 요청할 것입니다 (예 :Main메소드가 종료되고 앱이 종료됩니다). 이 시점에서 대기중인 작업이 시작될 때까지 기다리는 것이 좋습니다.SelfCancellingAsync그것이 정상적으로 끝내도록. 귀하의 접근 방식으로 이것이 어떻게 수행되는지 볼 수는 없습니다. - noseratio

-1

Task.Yield에 대한 대안이 아니다.Task.Factory.StartNew또는Task.Run. 그들은 완전히 다릅니다. 때를await Task.Yield스레드를 차단하지 않고 현재 스레드의 다른 코드를 실행할 수 있습니다. 그것을 기다리는 것 같이 생각하십시오.Task.Delay, 제외하고Task.Yield특정 시간 동안 기다리지 않고 작업이 완료 될 때까지 기다립니다.

참고 : 사용하지 마십시오.Task.YieldUI 스레드에서 UI가 항상 반응 형으로 유지된다고 가정합니다. 항상 그런 것은 아닙니다.


  • 나는 내가 어떻게 잘 이해 하는지를 믿는다.Task.Yield및 기타 사용자 정의 대기자가 작동합니다. 이 점에서,나는 대부분 당신의 마지막 문장 외에 모든 것에 동의하지 않습니다. - noseratio
  • "외Task.Yield특정 시간 동안 기다리는 것이 아니라 작업이 완료 될 때까지 기다립니다. "- 다른 작업이 완료 될 때까지 기다리지 않습니다. "준비가 완료되었습니다"연속을 예약하고 라인에서 대기중인 다음 "기존 준비"연속으로 전환합니다 (자체 일 수도 있음). 즉, 이것은 당신의 작업이 그 시점에서 동기가되는 것을 멈추게합니다. - binki

연결된 질문


관련된 질문

최근 질문