থ্রেড যখন কিছুক্ষণ লুপের মধ্যে কোনও কাজের জন্য অপেক্ষা করে তখন কী ঘটে?


10

কিছু সময়ের জন্য সি # এর অ্যাসিঙ্ক / প্রত্যাশার প্যাটার্নটির সাথে ডিল করার পরে, হঠাৎ আমি উপলব্ধি করেছিলাম যে নিম্নলিখিত কোডটিতে কী ঘটেছিল তা কীভাবে ব্যাখ্যা করতে হবে তা আমি সত্যিই জানি না:

async void MyThread()
{
    while (!_quit)
    {
        await GetWorkAsync();
    }
}

GetWorkAsync()প্রত্যাশিত প্রত্যাবর্তন হিসাবে ধরে নেওয়া হয় Taskযা ধারাবাহিকতা সম্পাদন করা হলে থ্রেড স্যুইচ হতে পারে বা নাও পারে।

অপেক্ষাটি কোনও লুপের ভিতরে না থাকলে আমি বিভ্রান্ত হব না। আমি স্বাভাবিকভাবেই আশা করতাম যে বাকী পদ্ধতিটি (অর্থাৎ ধারাবাহিকতা) সম্ভবত অন্য থ্রেডে চালিত হবে, যা ভাল fine

তবে, একটি লুপের ভিতরে, "পদ্ধতিটির বাকী অংশ" ধারণাটি আমার কাছে কিছুটা কুয়াশাচ্ছন্ন হয়ে পড়ে।

থ্রেডটি যদি ধারাবাহিকতা চালু রাখে তবে বনাম বনাম যদি এটি পরিবর্তন না হয় তবে কী হবে? লুপটির পরবর্তী পুনরাবৃত্তি কোন থ্রেডে সঞ্চালিত হয়?

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

আপডেট: আমার প্রশ্নটি সদৃশ নয়, কারওর দ্বারা পরামর্শ দেওয়া হয়েছে। while (!_quit) { ... }কোড প্যাটার্ন নিছক আমার প্রকৃত কোডের একটা সরলীকৃত ব্যবস্থা। বাস্তবে, আমার থ্রেডটি একটি দীর্ঘকালীন লুপ যা নিয়মিত বিরতিতে (ডিফল্টরূপে প্রতি 5 সেকেন্ড) তার কাজের আইটেমগুলির ইনপুট সারিটি প্রক্রিয়া করে। প্রকৃত প্রস্থান শর্ত চেক এছাড়াও নমুনা কোড দ্বারা প্রস্তাবিত হিসাবে সাধারণ ক্ষেত্র চেক নয়, বরং ইভেন্ট ইভেন্ট হ্যান্ডেল চেক।



1
আরও দেখুন কীভাবে NET- এ নিয়ন্ত্রণ প্রবাহ প্রয়োগ ও অপেক্ষার প্রবণতা রয়েছে? এটি কীভাবে একসাথে ওয়্যার্ড হয় সে সম্পর্কে কিছু দুর্দান্ত তথ্যের জন্য।
জন উ

@ জন উ: আমি এখনও এই থ্রেডটি দেখিনি। সেখানে প্রচুর আকর্ষণীয় তথ্য ন্যগেটস। ধন্যবাদ!
aoven

উত্তর:


6

আপনি আসলে ট্রাই রোজলিন এ এটি পরীক্ষা করে দেখতে পারেন । আপনার প্রতীক্ষিত পদ্ধতিটি void IAsyncStateMachine.MoveNext()জেনারেটেড অ্যাসিঙ্ক ক্লাসে আবার লিখিত হয় ।

আপনি যা দেখতে পাবেন তা হ'ল এইরকম:

            if (this.state != 0)
                goto label_2;
            //set up the state machine here
            label_1:
            taskAwaiter.GetResult();
            taskAwaiter = default(TaskAwaiter);
            label_2:
            if (!OuterClass._quit)
            {
               taskAwaiter = GetWorkAsync().GetAwaiter();
               //state machine stuff here
            }
            goto label_1;

মূলত, আপনি কোন থ্রেডে রয়েছেন তা বিবেচ্য নয়; স্টেট মেশিনটি আপনার লুপটি যদি / গোটো কাঠামোর সাথে সমতুল্য করে প্রতিস্থাপন করে সঠিকভাবে পুনরায় শুরু করতে পারে।

এটি বলার পরে, অ্যাসিঙ্ক পদ্ধতিগুলি অগত্যা কোনও ভিন্ন থ্রেডে চালিত হয় না। কীভাবে আপনি কেবল একটি থ্রেডে কাজ করতে পারবেন তা বোঝাতে এরিক লিপার্টের ব্যাখ্যা "এটি যাদু নয়" দেখুনasync/await


2
আমি মনে করি যে সংকলকটি আমার অ্যাসিঙ্ক কোডটিতে পুনরায় লেখার পরিমাণটি কম করে দেখছে। সংক্ষেপে, পুনর্লিখনের পরে কোনও "লুপ" নেই! এটা আমার জন্য অনুপস্থিত অংশ ছিল। 'রোজলিন চেষ্টা করুন' লিঙ্কটির জন্যও দুর্দান্ত এবং ধন্যবাদ!
aoven

গোটো হ'ল আসল লুপ কনস্ট্রাক্ট। আসুন ভুলে যাবেন না।

2

প্রথমত, সার্ভি একটি অনুরূপ প্রশ্নের উত্তরে কিছু কোড লিখেছেন, যার ভিত্তিতে এই উত্তরটি ভিত্তি করে:

/programming/22049339/how-to-create-a-cancellable-task-loop

সার্ভিয়ের উত্তরে ContinueWith()টিপিএল কনস্ট্রাক্টস asyncএবং awaitকীওয়ার্ডগুলির স্পষ্ট ব্যবহার ছাড়াই ব্যবহার করে একই ধরণের লুপ অন্তর্ভুক্ত রয়েছে ; সুতরাং আপনার প্রশ্নের উত্তর দেওয়ার জন্য, যখন আপনার লুপটি ব্যবহার না করে নিবন্ধভুক্ত করা হচ্ছে তখন আপনার কোডটি কী দেখতে পারে তা বিবেচনা করুনContinueWith()

    private static Task GetWorkWhileNotQuit()
    {
        var tcs = new TaskCompletionSource<bool>();

        Task previous = Task.FromResult(_quit);
        Action<Task> continuation = null;
        continuation = t =>
        {
            if (!_quit)
            {
                previous = previous.ContinueWith(_ => GetWorkAsync())
                    .Unwrap()
                    .ContinueWith(_ => previous.ContinueWith(continuation));
            }
            else
            {
                tcs.SetResult(_quit);
            }
        };
        previous.ContinueWith(continuation);
        return tcs.Task;
    }

আপনার মাথাটি চারদিকে মুড়িয়ে রাখতে কিছুটা সময় লাগে তবে সংক্ষেপে:

  • continuation"বর্তমান পুনরাবৃত্তি" এর জন্য একটি বন্ধের প্রতিনিধিত্ব করে
  • previousপ্রতিনিধিত্ব করে Taskরাজ্যের ধারণকারী "পূর্ববর্তী পুনরাবৃত্তির" (অর্থাত তা জানেন যখন 'পুনরাবৃত্তির' সমাপ্ত হয় এবং পরের এক শুরু করার জন্য ব্যবহার করা হয় ..)
  • প্রত্যাশিত GetWorkAsync()একটি Task, এর অর্থ 'অভ্যন্তরীণ টাস্ক' (যেমন প্রকৃত ফলাফল ) পেতে কলটি ContinueWith(_ => GetWorkAsync())ফিরে আসবে ।Task<Task>Unwrap()GetWorkAsync()

তাই:

  1. প্রাথমিকভাবে কোনও পূর্ববর্তী পুনরাবৃত্তি নেই, সুতরাং এটির একটি সহজ মূল্য দেওয়া হয় Task.FromResult(_quit) - এর রাজ্যটি শুরু হয় Task.Completed == true
  2. continuationব্যবহার প্রথমবারের চালানো হয়previous.ContinueWith(continuation)
  3. continuationঅবসান আপডেট previousসম্পন্ন রাষ্ট্র প্রতিফলিত_ => GetWorkAsync()
  4. যখন _ => GetWorkAsync()সম্পন্ন হয়, এটি "চালিয়ে যায়" _previous.ContinueWith(continuation)- অর্থাৎ continuationল্যাম্বডাকে আবার কল করে calling
    • স্পষ্টতই এই মুহুর্তে, previousরাষ্ট্রের সাথে আপডেট করা হয়েছে _ => GetWorkAsync()যাতে continuationলাম্বদা যখন ডাকে তখন ডাকা হয় GetWorkAsync()

continuationল্যামডা সবসময় রাজ্যের পরীক্ষা _quit, তাই যদি _quit == falseতারপর আর continuations হয়, এবং TaskCompletionSourceএর মানে সেট পরার _quit, এবং সবকিছু সম্পন্ন হয়।

অন্য ধারাবাহিকতায় ধারাবাহিকতা চালিত হওয়ার বিষয়ে আপনার পর্যবেক্ষণ হিসাবে, এটি async/ awaitকীওয়ার্ডটি আপনার জন্য কিছু করবে না, এই ব্লগ অনুসারে "টাস্কগুলি (এখনও) থ্রেড নয় এবং অ্যাসিঙ্ক সমান্তরাল নয়" । - https://blogs.msdn.mic Microsoft.com/benwilli/2015/09/10/tasks-are-still-not-threads-and-async-is-not-parallel/

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


2
আমি যে কোডটি দেখিয়েছি তা হ'ল (খুব) সরল। গেটওয়ার্কএন্সেঙ্ক () এর ভিতরে আরও একাধিক প্রতীক্ষার অপেক্ষা রয়েছে। তাদের মধ্যে কিছু ডাটাবেস এবং নেটওয়ার্ক অ্যাক্সেস করে যার অর্থ সত্য I / O। আমি যেমন বুঝতে পেরেছি, থ্রেড সুইচটি এমন অপেক্ষার প্রাকৃতিক পরিণতি (যদিও প্রয়োজন নেই), কারণ প্রাথমিক থ্রেড এমন কোনও সিঙ্ক্রোনাইজেশন প্রসঙ্গ প্রতিষ্ঠা করে না যেখানে ধারাবাহিকতাগুলি কার্যকর করা উচিত govern সুতরাং তারা একটি থ্রেড পুল থ্রেডে চালিত করে। আমার যুক্তি কি ভুল?
aoven

@ অওভেন গুড পয়েন্ট - আমি বিভিন্ন ধরণের বিবেচনা করি নি SynchronizationContext- এটি অবশ্যই গুরুত্বপূর্ণ যেহেতু .ContinueWith()ধারাবাহিকতা প্রেরণের জন্য সিঙ্ক্রোনাইজেশন কনটেক্সট ব্যবহার করে; এটি awaitথ্রেডপুল থ্রেড বা এএসপি.নেট থ্রেডে ডাকা হয় কিনা তা আপনি প্রকৃতপক্ষে আচরণের ব্যাখ্যা করবেন । এই ক্ষেত্রে অবশ্যই একটি ধারাবাহিকতা আলাদা থ্রেডে প্রেরণ করা যেতে পারে। অন্যদিকে, awaitডাব্লুপিএফ ডিসপ্যাচার বা উইনফর্মস প্রসঙ্গের মতো একক থ্রেডযুক্ত প্রসঙ্গকে কল করা মূলত ধারাবাহিকতাটি ঘটে তা নিশ্চিত করার জন্য পর্যাপ্ত পরিমাণে হওয়া উচিত। থ্রেড
বেন কট্রেল
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.