সি # 5 অ্যাসিঙ্ক সিটিপি: এন্ডআয়েট কলের আগে কেন অভ্যন্তরীণ "রাজ্য" তৈরি করা কোডে 0 সেট করা হয়?


195

গতকাল আমি নতুন সি # "অ্যাসিঙ্ক" বৈশিষ্ট্য সম্পর্কে একটি বক্তব্য দিচ্ছিলাম, বিশেষত উত্পন্ন কোডটি কীভাবে এবং the GetAwaiter()/ BeginAwait()/ EndAwait()কলগুলির মতো দেখাচ্ছে তা আবিষ্কার করে।

আমরা সি # সংকলক দ্বারা উত্পাদিত রাষ্ট্র মেশিনে কিছু বিশদে দেখেছি এবং দুটি দিক ছিল যা আমরা বুঝতে পারি না:

  • উত্পন্ন শ্রেণিতে কেন একটি Dispose()পদ্ধতি এবং একটি $__disposingভেরিয়েবল রয়েছে, যা কখনই ব্যবহৃত হয় না বলে মনে হয় (এবং শ্রেণিটি প্রয়োগ করে না IDisposable)।
  • অভ্যন্তরীণ stateভেরিয়েবলটি কোনও কল করার আগে কেন 0 তে সেট করা হয় EndAwait(), যখন 0 সাধারণত প্রদর্শিত হয় "এটিই প্রাথমিক প্রবেশ পয়েন্ট" mean

আমি সন্দেহ করি যে অ্যাসিঙ্ক পদ্ধতিতে আরও আকর্ষণীয় কিছু করে প্রথম পয়েন্টটির উত্তর দেওয়া যেতে পারে, যদিও কারও কাছে আরও কিছু তথ্য থাকলে আমি তা শুনে খুশি হব। এই প্রশ্নটি তবে দ্বিতীয় পয়েন্টটি সম্পর্কে আরও বেশি।

এখানে নমুনা কোডের একটি খুব সাধারণ টুকরো:

using System.Threading.Tasks;

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

... এবং এখানে কোডটি সেই MoveNext()পদ্ধতির জন্য উত্পন্ন হয় যা রাষ্ট্র মেশিন প্রয়োগ করে। এটি সরাসরি প্রতিফলকের কাছ থেকে অনুলিপি করা হয়েছে - আমি অব্যক্ত পরিবর্তনশীল নামগুলি স্থির করিনি:

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) এটি আবার অ্যাসিঙ্ক পদ্ধতিটি কার্যকরভাবে আবার শুরু করবে, যা আমি যতটা বলতে পারি যথাযথভাবে অনুপযুক্ত ... যদি বলা MoveNext() হয় না এবং রাজ্য পরিবর্তন অপ্রাসঙ্গিক।

এটি কি কেবল এসিঙ্কের জন্য পুনরাবৃত্তকারী ব্লক জেনারেশন কোডটি পুনরায় ব্যবহার করে সংকলকটির একটি পার্শ্ব-প্রতিক্রিয়া, যেখানে এর আরও স্পষ্ট ব্যাখ্যা থাকতে পারে?

গুরুত্বপূর্ণ অস্বীকৃতি

অবশ্যই এটি কেবল একটি সিটিপি সংকলক। আমি চূড়ান্ত প্রকাশের আগে জিনিসগুলি পরিবর্তনের পুরোপুরি প্রত্যাশা করি - এবং সম্ভবত পরবর্তী সিটিপি প্রকাশের আগেও। এই প্রশ্নটি কোনওভাবেই দাবি করার চেষ্টা করছে না এটি সি # সংকলক বা এর মতো অন্য কোনও ত্রুটি। আমি কেবল এটি মিস করার কোনও সূক্ষ্ম কারণ আছে কিনা তা নিয়ে চেষ্টা করার চেষ্টা করছি :)


7
ভিবি সংকলক অনুরূপ রাষ্ট্রীয় মেশিন তৈরি করে (এটি প্রত্যাশিত কিনা তা জানেন না তবে ভিবি এর আগে
পুনরুদ্ধারকারী

1
@ রুন: মুভনেক্সটডেলিগেটটি কেবল একটি প্রতিনিধি ক্ষেত্র যা মুভনেেক্সটকে বোঝায়। আমি প্রতিবার অপেক্ষা করার জন্য একটি নতুন অ্যাকশন তৈরি এড়াতে ক্যাশে করা হয়েছে, আমি বিশ্বাস করি।
জন স্কিটি

5
আমি মনে করি উত্তরটি: এটি একটি সিটিপি। দলের জন্য হাই-অর্ডার বিটটি এটি এখানে পেয়েছে এবং ভাষার নকশাটি বৈধ হয়েছে। এবং তারা এত আশ্চর্যজনকভাবে দ্রুত কাজ করেছিল। আপনার শিপড বাস্তবায়ন (কম্পাইলারগুলির, মুভনেক্সট নয়) উল্লেখযোগ্যভাবে আলাদা হওয়ার আশা করা উচিত। আমি মনে করি যে এরিক বা লুসিয়ান এই উত্তরটির সাথে উত্তরটি ফিরে আসতে চাইবে যে এখানে গভীর কিছু নেই, কেবল এমন একটি আচরণ / বাগ যা বেশিরভাগ ক্ষেত্রেই গুরুত্বপূর্ণ নয় এবং কারও নজরে নেই। কারণ এটি একটি সিটিপি।
ক্রিস বুড়ো

2
@ স্টিলগার: আমি সবেমাত্র ইল্ডাসম পরীক্ষা করেছি এবং এটি সত্যিই এটি করছে।
জন স্কিটি

3
@ জোনস্কিট: লক্ষ্য করুন কীভাবে কেউ উত্তরগুলি সমর্থন করে না। আমাদের মধ্যে 99% সত্য উত্তর দিতে পারে কিনা তা সঠিকভাবে বলতে পারে না।
দ্য_ড্রো

উত্তর:


71

ঠিক আছে, শেষ পর্যন্ত আমার কাছে একটি সত্য উত্তর আছে। আমি এটিকে নিজের মতো করে কাজ করেছি, তবে কেবলমাত্র দলের ভিবি অংশের লুসিয়ান উইশিক নিশ্চিত হওয়ার পরেই সত্য যে এর পক্ষে যুক্তিসঙ্গত কারণ রয়েছে। তাকে অনেক ধন্যবাদ - এবং দয়া করে তার ব্লগটি দেখুন , যা দুলছে।

এখানে 0 মানটি কেবলমাত্র বিশেষ কারণ এটি না একটি বৈধ রাষ্ট্র যা আপনি শুধু সামনে হতে পারে awaitএকটি স্বাভাবিক ক্ষেত্রে। বিশেষত, এটি এমন কোনও রাষ্ট্র নয় যা রাষ্ট্রের মেশিনটি অন্য কোথাও পরীক্ষা চালিয়ে যেতে পারে। আমি বিশ্বাস করি যে কোনও ধনাত্মক মান ব্যবহার করা ঠিক তেমনি কাজ করবে: -1 এটির জন্য যুক্তিযুক্তভাবে ভুল হিসাবে ব্যবহার করা হয় না , কারণ -1 এর অর্থ সাধারণত "সমাপ্ত" হয়। আমি তর্ক করতে পারি যে আমরা এই মুহুর্তে 0 টির জন্য একটি অতিরিক্ত অর্থ দিচ্ছি, তবে শেষ পর্যন্ত এটি আসলে কোনও ব্যাপার নয়। এই প্রশ্নের মূল বিষয়টি সন্ধান করছিল কেন রাজ্যটি আদৌ সেট করা হচ্ছে।

মানটি প্রাসঙ্গিক হয় যদি অপেক্ষাটি কোনও ব্যতিক্রম ঘটে যা ধরা পড়ে in আমরা আবার একই প্রত্যাশার বিবৃতিতে ফিরে এসে শেষ করতে পারি, তবে আমাদের "রাজ্যটিতে কেবল অপেক্ষা করা থেকে ফিরে আসার অর্থ" হওয়া উচিত নয়, অন্যথায় সমস্ত ধরণের কোড বাদ দেওয়া হবে। এটি একটি উদাহরণ সহ প্রদর্শিত সহজ। মনে রাখবেন যে আমি এখন দ্বিতীয় সিটিপি ব্যবহার করছি, সুতরাং উত্পন্ন কোডটি প্রশ্নের মধ্যে কিছুটা আলাদা।

এখানে অ্যাসিঙ্ক পদ্ধতি:

static async Task<int> FooAsync()
{
    var t = new SimpleAwaitable();

    for (int i = 0; i < 3; i++)
    {
        try
        {
            Console.WriteLine("In Try");
            return await t;
        }                
        catch (Exception)
        {
            Console.WriteLine("Trying again...");
        }
    }
    return 0;
}

ধারণামূলকভাবে SimpleAwaitable যে কোনও অপেক্ষারত হতে পারে - হতে পারে কোনও কাজ, সম্ভবত অন্য কিছু। আমার পরীক্ষার উদ্দেশ্যে, এটি সর্বদা মিথ্যা প্রদান করে IsCompletedএবং এর ব্যতিক্রম ছুঁড়ে ফেলে GetResult

এর জন্য উত্পন্ন কোড এখানে MoveNext :

public void MoveNext()
{
    int returnValue;
    try
    {
        int num3 = state;
        if (num3 == 1)
        {
            goto Label_ContinuationPoint;
        }
        if (state == -1)
        {
            return;
        }
        t = new SimpleAwaitable();
        i = 0;
      Label_ContinuationPoint:
        while (i < 3)
        {
            // Label_ContinuationPoint: should be here
            try
            {
                num3 = state;
                if (num3 != 1)
                {
                    Console.WriteLine("In Try");
                    awaiter = t.GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        state = 1;
                        awaiter.OnCompleted(MoveNextDelegate);
                        return;
                    }
                }
                else
                {
                    state = 0;
                }
                int result = awaiter.GetResult();
                awaiter = null;
                returnValue = result;
                goto Label_ReturnStatement;
            }
            catch (Exception)
            {
                Console.WriteLine("Trying again...");
            }
            i++;
        }
        returnValue = 0;
    }
    catch (Exception exception)
    {
        state = -1;
        Builder.SetException(exception);
        return;
    }
  Label_ReturnStatement:
    state = -1;
    Builder.SetResult(returnValue);
}

আমাকে চলাফেরা করতে হয়েছিল Label_ContinuationPoint এটিকে বৈধ কোড হিসাবে চালিত করতে হয়েছিল - অন্যথায় এটি gotoবিবৃতিটির আওতায় নেই - তবে এটি উত্তরটিকে প্রভাবিত করে না।

GetResultএর ব্যতিক্রম ছোঁড়া হলে কী ঘটে তা ভেবে দেখুন । আমরা ক্যাচ ব্লক, ইনক্রিমেন্ট iএবং তারপরে আবার লুপ করব (ধরে iনিচ্ছি এখনও 3 এরও কম)। আমরা GetResultকল করার আগে যে অবস্থায় ছিলাম আমরা এখনও রয়েছি ... তবে tryব্লকের ভিতরে গেলে আমাদের অবশ্যই "ইন ট্রাই" মুদ্রণ করতে হবে এবং GetAwaiterআবার কল করতে হবে ... এবং রাষ্ট্রটি 1 না হলে আমরা কেবল এটি করব Without দ্যstate = 0 নিয়োগ, এটা বিদ্যমান awaiter ব্যবহার এবং এড়িয়ে যাবে Console.WriteLineকল।

এটি কাজ করার জন্য কোডটি মোটামুটি টর্সুয়াস বিট, তবে এটি কেবল দলটির যে ধরণের জিনিস চিন্তা করতে হবে তা দেখায়। আমি আনন্দিত যে আমি এটি বাস্তবায়নের জন্য দায়ী নই :)


8
@ শেখার_প্রো: হ্যাঁ, এটা একটা দুর্দান্ত অটো উত্পাদিত রাষ্ট্রের মেশিনগুলিতে আপনার প্রচুর বিবৃতি দেখার আশা করা উচিত :)
জন স্কিটি

12
@ শেখার_প্রো: ম্যানুয়ালি লিখিত কোডের মধ্যে এটি হ'ল - কারণ এটি কোডটি পড়া এবং অনুসরণ করা শক্ত করে তোলে। আমার মত বোকা যারা এটিকে ছড়িয়ে দেয় কেবল তা ছাড়া কেউই স্বয়ংক্রিয় জেনারেটেড কোডটি পড়ে না :)
জন স্কিটি

তাই কি করে ঘটতে যখন আমরা একটি ব্যতিক্রম পরে আবার অপেক্ষা? আমরা আবার সব শুরু?
Configurator

1
@ কনফিগ্রেটর: এটি প্রত্যাশিতদের জন্য গেটএইউটারকে কল করে, যা আমি এটি করবো বলে আশা করি।
জন স্কিটি

Goos কোড পড়ার জন্য সবসময় কঠিন করে না। আসলে, কখনও কখনও তারা এমনকি ব্যবহার করতে বুদ্ধি করে (বলার জন্য বলি, আমি জানি)। উদাহরণস্বরূপ, কখনও কখনও আপনার একাধিক নেস্টেড লুপগুলি ভাঙ্গতে হতে পারে। আমি গোটোর কম ব্যবহৃত বৈশিষ্ট্য (এবং আইএমও কৃপযুক্ত ব্যবহার) ক্যাসকেডে স্যুইচ স্টেটমেন্টগুলির কারণ হতে পারে। একটি বিচ্ছিন্ন নোটে, আমি একটি দিন এবং যুগের কথা মনে করি যখন গোটোস কিছু প্রোগ্রামিং ভাষার প্রাথমিক আন্ডারপিনিং ছিল এবং সে কারণেই আমি পুরোপুরি বুঝতে পারি যে কেন গোটোটির উল্লেখ কেবল উল্লেখ করা বিকাশকারীদেরকে কাঁপিয়ে তোলে। যদি তারা খারাপভাবে ব্যবহার করা হয় তবে তারা জিনিসগুলিকে কুৎসিত করতে পারে।
বেন লেশ

5

যদি এটি 1 এ রাখা হয় (প্রথম ক্ষেত্রে) আপনি কল না EndAwaitকরেই কল পাবেন BeginAwait। যদি এটি ২ (দ্বিতীয় কেস) এ রাখা থাকে আপনি কেবল অন্য ওয়েটারে একই ফল পাবেন।

আমি অনুমান করছি যে বিগনিওয়েটকে কল করা যদি এটি ইতিমধ্যে শুরু হয়ে যায় তবে (আমার দিক থেকে একটি অনুমান) মিথ্যা ফেরত দেয় এবং এন্ডএইয়েটে ফিরে আসার মূল মান রাখে। যদি এটি হয় তবে এটি সঠিকভাবে কাজ করবে আপনি যদি এটি সেট করে -1 এ সেট করেন তবে আপনার অবিচ্ছিন্ন থাকতে পারেthis.<1>t__$await1 প্রথম ক্ষেত্রে ।

তবে এটি ধরে নেওয়া হয় যে বিগিনিউয়েটার আসলে প্রথমটির পরে কোনও কলগুলিতে অ্যাকশন শুরু করবে না এবং এটি এই ক্ষেত্রে মিথ্যা ফিরিয়ে দেবে। এটি অবশ্যই পার্শ্ব প্রতিক্রিয়া হতে পারে বা কেবল একটি পৃথক ফলাফল দিতে পারে কারণ অগ্রহণযোগ্য হবে। এটি এটিও ধরে নিয়েছে যে এন্ডএয়েটার যতবার ডাকা হোক না কেন এটি সর্বদা একই মান প্রদান করবে এবং যখন বিগিনিএয়েট মিথ্যা দেখায় তখনই এটি বলা যেতে পারে (উপরের অনুমান অনুসারে)

এটি বর্ণের শর্তগুলির বিরুদ্ধে একটি প্রহরী বলে মনে হবে যদি আমরা স্টেটনেেক্সটকে রাজ্য = 0 পরে বিভিন্ন থ্রেড দ্বারা ডাকা যেখানে স্টেটমেন্টগুলি ইনলাইন করে থাকি তবে এটি নীচের মতো কিছু দেখায়

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

যদি উপরের অনুমানগুলি সঠিক হয়ে যায় তবে কিছু অপ্রয়োজনীয় কাজ করা হয়েছে যেমন সেরিয়াটার পান এবং একই মানটিকে <1> টি __ $ ওয়েভিট 1 এ পুনরায় অর্পণ করা। রাজ্যটি যদি 1 এ রাখা হয় তবে শেষ অংশটি স্থির থাকবে:

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

আরও যদি এটি 2 এ সেট করা থাকে তবে রাষ্ট্রীয় মেশিনটি ধরে নেবে যে এটি ইতিমধ্যে প্রথম ক্রিয়াটির মূল্য অর্জন করেছে যা অসত্য হবে এবং ফলাফলটি গণনা করার জন্য একটি (সম্ভাব্য) নিরীক্ষিত ভেরিয়েবল ব্যবহার করা হবে


মনে রাখবেন যে রাজ্যটি আসলে 0 এবং অ্যাসাইনমেন্টের সাথে আরও অর্থবহ মানের ক্ষেত্রে ব্যবহৃত হচ্ছে না । যদি এটি বর্ণের শর্তগুলির বিরুদ্ধে রক্ষা করা বোঝায় তবে আমি মুভি নেক্সটের শুরুতে অনুপযুক্ত ব্যবহার শনাক্ত করার জন্য এটির জন্য একটি চেক সহ উদাহরণস্বরূপ আরও কিছু মান উল্লেখ করব। মনে রাখবেন যে একক দৃষ্টান্তটি আসলে একবারে দুটি থ্রেড দ্বারা কখনই ব্যবহার করা উচিত নয় - এর অর্থ এমন একক সিঙ্ক্রোনাস মেথড কলটির মায়া দেওয়া যা প্রায়শই "বিরতি" দেয়।
জন স্কিটি

@ জনের আমি সম্মত হই যে এটি অ্যাসিঙ্কের ক্ষেত্রে দৌড় শর্ত নিয়ে সমস্যা হওয়া উচিত নয় তবে এটি পুনরাবৃত্তির ব্লক হতে পারে এবং একটি বাম ওভার হতে পারে
রুন এফএস

@ টনি: আমি মনে করি আমি পরবর্তী সিটিপি বা বিটা বের না হওয়া পর্যন্ত অপেক্ষা করব এবং সেই আচরণটি পরীক্ষা করব।
জন স্কিটি

1

স্ট্যাকড / নেস্টেড অ্যাসিঙ্ক কলগুলি দিয়ে কি কিছু করা যায়? ..

অর্থাৎ,

async Task m1()
{
    await m2;
}

async Task m2()
{
    await m3();
}

async Task m3()
{
Thread.Sleep(10000);
}

মুভনেস্ট ডেলিগেট এই পরিস্থিতিতে একাধিকবার কল করা হবে?

সত্যিই কি একটা প্যান্ট?


এক্ষেত্রে তিনটি আলাদা উত্পন্ন শ্রেণি থাকবে। MoveNext()তাদের প্রত্যেককে একবার ডাকা হবে।
জন স্কিটি

0

প্রকৃত রাজ্যের ব্যাখ্যা:

সম্ভাব্য রাষ্ট্রগুলি:

  • 0 শুরু হয়েছে (আমারও তাই মনে হয়) বা অপারেশন শেষ হওয়ার অপেক্ষায়
  • > 0 কেবলমাত্র মুভনেসেক্সট নামে ডাকা হবে, পরের স্টেটটি বেছে নিবে
  • -1 শেষ

এই বাস্তবায়নটি কেবল এই আশ্বাস দিতে চায় যে, যে কোনও জায়গা থেকে মুভনেক্সটে কল করা হলে (অপেক্ষা করার সময়) এটি শুরু থেকে পুরো রাজ্য-চেইনের পুনরায় মূল্যায়ন করবে, ফলাফলগুলি পুনর্বিবেচনা করতে যা ইতিমধ্যে পুরানো সময়ের মধ্যে হতে পারে?


তবে কেন এটি শুরু থেকেই শুরু করতে চাইবে? এটি আসলে যা ঘটেছিল তা প্রায় নয় - আপনি একটি ব্যতিক্রম ছুঁড়ে ফেলতে চান, কারণ অন্য কোনও কিছুই মুভনেক্সটকে কল করা উচিত নয় ।
জন স্কিটি 6
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.