[c#] C # 5 비동기 CTP : EndAwait 호출 전에 생성 된 코드에서 내부 "상태"가 0으로 설정된 이유는 무엇입니까?


Answers

그것이 1로 유지되면 (첫 번째 경우) EndAwait 에 대한 호출없이 EndAwait 에 대한 호출이 BeginAwait 합니다. 2로 유지하면 (두 번째 경우) 다른 대기자에게도 동일한 결과가 나타납니다.

BeginAwait 호출이 이미 시작 되었다면 (내 측에서는 추측) 원래 값을 EndAwait에서 반환하도록 유지하면 false를 반환한다고 생각합니다. 그럴 경우 제대로 작동하지만 -1로 설정하면 초기화되지 않은 것일 수 있습니다 this.<1>t__$await1 첫 번째 경우에는 this.<1>t__$await1 입니다.

그러나 BeginAwaiter는 첫 번째 호출 이후 BeginAwaiter가 실제로 어떤 동작에서도 동작을 시작하지 않으며,이 경우에 false를 반환한다고 가정합니다. 시작은 부작용이 있거나 단순히 다른 결과를 줄 수 있기 때문에 용인 될 수 없습니다. 또한 EndAwaiter는 호출 횟수에 관계없이 항상 동일한 값을 반환하고 BeginAwait이 false를 반환하면 호출 될 수 있음을 상기합니다 (위 가정에 따라)

그것은 경주 조건에 대한 경비원처럼 보입니다. 질문에서 state = 0 후에 다른 스레드가 movenext를 호출하는 문장을 인라인하면 다음과 같은 모양이됩니다

this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate)
this.<>1__state = 0;

//second thread
this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate)
this.$__doFinallyBodies = true;
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

//other thread
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

위의 가정이 맞다면 sawiater를 받고 같은 값을 <1> t __ $ await1에 재 할당하는 것과 같은 불필요한 작업이 있습니다. 상태가 1로 유지되면 마지막 부분은 다음과 같이됩니다.

//second thread
//I suppose this un matched call to EndAwait will fail
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

더 나아가 그것이 2로 설정되면 상태 머신은 그것이 사실이 아닐 수있는 첫 번째 액션의 값을 이미 얻었으며 (잠재적으로) 할당되지 않은 변수가 결과를 계산하는 데 사용될 것이라고 가정합니다

Question

어제 저는 새로운 C # "async"기능, 특히 생성 된 코드가 어떻게 the GetAwaiter() / BeginAwait() / EndAwait() 호출에 대해 설명했습니다.

우리는 C # 컴파일러에 의해 생성 된 상태 머신을 자세히 살펴 보았습니다. 우리가 이해할 수 없었던 두 가지 측면이있었습니다.

  • 생성 된 클래스에 Dispose() 메서드와 사용되지 않는 $__disposing 변수가 들어있는 이유는 무엇입니까 (클래스는 IDisposable 구현하지 않습니다).
  • EndAwait() 호출하기 전에 내부 state 변수가 0으로 설정되는 이유는 일반적으로 0이 "이것이 초기 진입 점"을 의미하는 것으로 나타나는 경우입니다.

누군가가 더 많은 정보를 가지고 있다면 나는 그것을 듣기 때문에 기뻐할 것이지만, 비동기 방법에서 더 흥미로운 것을함으로써 첫 번째 요점을 대답 할 수 있을지 의심 스럽다. 이 질문은 두 번째 요점에 관한 것입니다.

다음은 매우 간단한 샘플 코드입니다.

using System.Threading.Tasks;

class Test
{
    static async Task<int> Sum(Task<int> t1, Task<int> t2)
    {
        return await t1 + await t2;
    }
}

... 여기 상태 코드를 구현하는 MoveNext() 메서드에 대해 생성되는 코드가 있습니다. 이것은 Reflector에서 직접 복사됩니다 - 말할 수없는 변수 이름을 수정하지 않았습니다 :

public void MoveNext()
{
    try
    {
        this.$__doFinallyBodies = true;
        switch (this.<>1__state)
        {
            case 1:
                break;

            case 2:
                goto Label_00DA;

            case -1:
                return;

            default:
                this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
                this.<>1__state = 1;
                this.$__doFinallyBodies = false;
                if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
                {
                    return;
                }
                this.$__doFinallyBodies = true;
                break;
        }
        this.<>1__state = 0;
        this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
        this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
        this.<>1__state = 2;
        this.$__doFinallyBodies = false;
        if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
        {
            return;
        }
        this.$__doFinallyBodies = true;
    Label_00DA:
        this.<>1__state = 0;
        this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
        this.<>1__state = -1;
        this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
    }
    catch (Exception exception)
    {
        this.<>1__state = -1;
        this.$builder.SetException(exception);
    }
}

길기는하지만이 질문에 대한 중요한 내용은 다음과 같습니다.

// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();

// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();

두 경우 모두 다음에 분명히 관찰되기 전에 상태가 다시 변경됩니다 ... 그래서 0으로 설정하는 이유는 무엇입니까? MoveNext() 가이 시점에서 (직접 또는 Dispose 를 통해) 다시 호출되면 비동기 메서드를 다시 시작합니다. 이는 내가 알 수있는 한 완전히 부적절합니다. if 및 MoveNext() 호출 되지 않고 , 국가의 변화는 무의미합니다.

이것은 단순히 비동기에 대한 반복기 블록 생성 코드를 재사용하는 컴파일러의 부작용입니까? 더 명확한 설명이있을 수 있습니다.

중요한 면책 조항

분명히 이것은 CTP 컴파일러 일뿐입니다. 나는 최종 릴리즈 이전에, 그리고 아마도 다음 CTP 릴리즈 전에도 상황이 바뀔 것으로 완전히 기대하고 있습니다. 이 질문은 C # 컴파일러 또는 이와 유사한 것에 대한 결함이라고 주장하려는 것이 아닙니다. 나는 내가 놓친이 미묘한 이유가 있는지 여부를 알아 내려고 노력하고있다. :)




실제 상태에 대한 설명 :

가능한 상태 :

  • 0 초기화 (내 생각으로) 또는 작동 종료를 기다리고 있습니다.
  • > 0 그냥 MoveNext 호출, 다음 상태를 chosing
  • -1 종료 됨

이 구현을 통해 MoveNext에 대한 다른 Call이 발생했다면 (기다리는 동안) 처음부터 전체 국가 체인을 다시 평가하고, 이미 구식 일 수있는 결과를 재평가 하도록 보장 할 수 있습니까?




Links