কেন এই অ্যাসিঙ্ক ক্রিয়াটি স্থগিত হয়?


103

আমার কাছে একটি বহু-স্তর রয়েছে। নেট 4.5 অ্যাপ্লিকেশনটিতে সি # এর নতুন asyncএবং awaitকীওয়ার্ড যা কেবলমাত্র স্তব্ধ হয়ে থাকে তা ব্যবহার করে একটি পদ্ধতি কল করে এবং কেন তা দেখতে পাচ্ছি না।

নীচে আমার একটি অ্যাসিঙ্ক পদ্ধতি রয়েছে যা আমাদের ডাটাবেস ইউটিলিটিটি OurDBConn(মূলত অন্তর্নিহিত DBConnectionএবং DBCommandঅবজেক্টগুলির জন্য একটি মোড়ক )কে ছাড়িয়ে যায়:

public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
    string connectionString = dataSource.ConnectionString;

    // Start the SQL and pass back to the caller until finished
    T result = await Task.Run(
        () =>
        {
            // Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
            using (var ds = new OurDBConn(connectionString))
            {
                return function(ds);
            }
        });

    return result;
}

তারপরে আমার কাছে মাঝারি স্তরের অ্যাসিঙ্ক পদ্ধতি রয়েছে যা কিছু ধীর গতিতে চলতে মোট যোগ দিতে এটিকে কল করে:

public static async Task<ResultClass> GetTotalAsync( ... )
{
    var result = await this.DBConnection.ExecuteAsync<ResultClass>(
        ds => ds.Execute("select slow running data into result"));

    return result;
}

অবশেষে আমার কাছে একটি ইউআই পদ্ধতি আছে (একটি এমভিসি অ্যাকশন) যা সুসংগতভাবে চলে:

Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);

// do other stuff that takes a few seconds

ResultClass slowTotal = asyncTask.Result;

সমস্যাটি এটি চূড়ান্তভাবে শেষ পংক্তিতে ঝুলে থাকে। আমি ফোন করলে এটি একই কাজ করে asyncTask.Wait()। আমি যদি ধীর এসকিউএল পদ্ধতিটি সরাসরি চালাই তবে এটি প্রায় 4 সেকেন্ড সময় নেয়।

আমি যে আচরণটি প্রত্যাশা করছি তা হ'ল এটি যখন হয়ে যায়, asyncTask.Resultএটি শেষ না হলে এটি হওয়া অবধি অপেক্ষা করা উচিত এবং এটি একবার হলে ফলাফলটি ফিরে আসা উচিত।

যদি আমি কোনও ডিবাগার দিয়ে এসকিউএল স্টেটমেন্টটি সম্পন্ন করি এবং ল্যাম্বদা ফাংশন শেষ হয়ে যায় তবে return result;লাইনটি GetTotalAsyncকখনই পৌঁছায় না।

আমি কি ভুল করছি কোন ধারণা?

এটি ঠিক করার জন্য আমার তদন্তের প্রয়োজন যেখানে কোনও পরামর্শ?

এটি কি কোথাও অচলাবস্থা হতে পারে এবং যদি তাই হয় তবে এটির সন্ধানের কোনও সরাসরি উপায় আছে কি?

উত্তর:


151

হ্যাঁ, এটা ঠিক আছে। এবং টিপিএল একটি সাধারণ ভুল, তাই খারাপ লাগবে না।

আপনি যখন লেখেন await foo, রানটাইম, ডিফল্টরূপে, পদ্ধতিটি শুরু হওয়া একই সিঙ্ক্রোনাইজেশন কনটেক্সটে ফাংশনের ধারাবাহিকতার সূচি করে। ইংরাজীতে, ধরা যাক আপনি ExecuteAsyncইউআই থ্রেড থেকে আপনার ফোন করেছেন । আপনার ক্যোয়ারী থ্রেডপুলের থ্রেডে চলেছে (কারণ আপনি বলেছিলেন Task.Run), তবে আপনি ফলাফলের জন্য অপেক্ষা করছেন। এর অর্থ return result;রানটটাইম থ্রেডপুলে পুনরায় নির্ধারণের পরিবর্তে ইউআই থ্রেডে ফিরে চালানোর জন্য আপনার লাইনটি " " শিডিয়ুল করবে।

তাহলে এই অচলাবস্থা কীভাবে? কল্পনা করুন আপনার সবেমাত্র এই কোডটি রয়েছে:

var task = dataSource.ExecuteAsync(_ => 42);
var result = task.Result;

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

এটি টিপিএল ব্যবহারের মূল নিয়মের চিত্র তুলে ধরে: আপনি যখন .Resultকোনও ইউআই থ্রেড ব্যবহার করেন (বা অন্য কোনও অভিনব সিঙ্ক প্রসঙ্গে), আপনার অবশ্যই অবশ্যই সতর্কতা অবলম্বন করতে হবে যে টাস্কের উপর নির্ভরশীল কোনও কিছুই ইউআই থ্রেডের সাথে নির্ধারিত নয়। অন্যথায় দুষ্টতা ঘটে।

তো তুমি কি কর? # 1 বিকল্পটি সর্বত্র ব্যবহারের জন্য অপেক্ষা করা হয়েছে, তবে আপনি যেমনটি বলেছেন যে এটি ইতিমধ্যে কোনও বিকল্প নয়। দ্বিতীয় বিকল্প যা আপনার জন্য উপলভ্য তা হল কেবল অপেক্ষা করা বন্ধ করা। আপনি আপনার দুটি ফাংশন এতে নতুন করে লিখতে পারেন:

public static Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
    string connectionString = dataSource.ConnectionString;

    // Start the SQL and pass back to the caller until finished
    return Task.Run(
        () =>
        {
            // Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
            using (var ds = new OurDBConn(connectionString))
            {
                return function(ds);
            }
        });
}

public static Task<ResultClass> GetTotalAsync( ... )
{
    return this.DBConnection.ExecuteAsync<ResultClass>(
        ds => ds.Execute("select slow running data into result"));
}

পার্থক্য কি? এখন আর কোথাও অপেক্ষার অপেক্ষা রাখে না, সুতরাং কিছুই ইউআই থ্রেডের সাথে অন্তর্ভুক্ত নয়। এর মতো সহজ পদ্ধতির জন্য যাদের একক ফিরতে হবে, " var result = await...; return result" বিন্যাস করার কোনও অর্থ নেই ; কেবলমাত্র async সংশোধক সরান এবং সরাসরি টাস্ক অবজেক্টটি পাস করুন। এটি ওভারহেড কম, অন্য কিছু না হলে।

বিকল্প # 3টি উল্লেখ করা হচ্ছে যে আপনি আপনার অপেক্ষাগুলি ইউআই থ্রেডে ফিরে নির্ধারণ করতে চান না, তবে কেবল থ্রেড পুলে শিডিয়ুল করুন। আপনি ConfigureAwaitপদ্ধতিটি দিয়ে এটি করুন , যেমন:

public static async Task<ResultClass> GetTotalAsync( ... )
{
    var resultTask = this.DBConnection.ExecuteAsync<ResultClass>(
        ds => return ds.Execute("select slow running data into result");

    return await resultTask.ConfigureAwait(false);
}

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


6
বিটিডাব্লু, প্রশ্নটি এএসপি.এনইটি সম্পর্কে, সুতরাং কোনও ইউআই থ্রেড নেই। তবে এএসপি.এনইটি-র কারণে ডেডলকগুলি নিয়ে সমস্যাটি একই রকম SynchronizationContext
সুইভ

এটি অনেকটা ব্যাখ্যা করেছিল, যেমন আমার মতোই ছিল। নেট 4 কোডে সমস্যা নেই তবে এটি async/ awaitকীওয়ার্ড ছাড়াই টিপিএল ব্যবহার করেছে ।
কিথ

4
টিপিএল = টাস্ক সমান্তরাল গ্রন্থাগার এমএসডিএন.ইমক্রোসফট /en-us/library/dd460717(v=vs.110).aspx
জেমি আইডে

যদি কেউ ভিবিএন কোড খুঁজছেন (আমার মতো) তবে এখানে তা ব্যাখ্যা করা হয়েছে: ডকস.মাইক্রোসফট
মাইকেল

আপনি কি দয়া করে আমাকে স্ট্যাকওভারফ্লো
জিতেন্দ্র পাঁচোলি

36

আমি আমার ব্লগে যেমন বর্ণনা করেছি এটি ক্লাসিক মিশ্র- asyncঅচলাবস্থার দৃশ্য । জেসন এটিকে ভালভাবে বর্ণনা করেছেন: ডিফল্টরূপে, একটি "প্রসঙ্গ" সর্বদা সংরক্ষণ করা হয় এবং পদ্ধতিটি চালিয়ে যাওয়ার জন্য ব্যবহৃত হয় । এই "প্রসঙ্গটি" বর্তমান না থাকলে এটি বর্তমান , যে ক্ষেত্রে এটি বর্তমান । যখন অব্যাহত রাখার জন্য পদ্ধতি প্রচেষ্টা, এটা প্রথম পুনরায় প্রবেশ বন্দী "প্রসঙ্গ" (এই ক্ষেত্রে, একটি ASP.NET )। এএসপি.এনইটি একবারে প্রসঙ্গে কেবল একটি থ্রেডকে অনুমতি দেয় এবং ইতিমধ্যে প্রসঙ্গে একটি থ্রেড রয়েছে - থ্রেডটি ব্লক করা আছে ।awaitasyncSynchronizationContextnullTaskSchedulerasyncSynchronizationContextSynchronizationContextTask.Result

দুটি নির্দেশিকা যা এই অচলাবস্থা এড়াতে পারবে:

  1. নিচে asyncসমস্ত উপায় ব্যবহার করুন । আপনি উল্লেখ করেছেন যে আপনি এটি করতে "পারবেন না", তবে কেন তা করবেন তা আমি নিশ্চিত নই। .NET 4.5 এএসপি.নেট এমভিসি অবশ্যই asyncক্রিয়াকে সমর্থন করতে পারে এবং এটি করা কোনও কঠিন পরিবর্তন নয়।
  2. ConfigureAwait(continueOnCapturedContext: false)যতটা সম্ভব ব্যবহার করুন । এটি ক্যাপচারিত প্রসঙ্গে পুনরায় শুরু করার ডিফল্ট আচরণকে ওভাররাইড করে।

কি ConfigureAwait(false)গ্যারান্টি যা বর্তমান ফাংশন একটি ভিন্ন প্রেক্ষাপটে তারিখে পুনরায় শুরু হবে?
চিউ x

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

কেবল আমার মন্তব্যটি স্পষ্ট করতে - আমি আগ্রহী ছিলাম যদি ConfigureAwait(false)কল ট্রি ব্যবহার করে ওপি'র সমস্যাটি সমাধান করা যেত।
চিউ x x

4
@ কিথ: এমভিসি ক্রিয়া করা asyncক্লায়েন্টের পক্ষে মোটেই প্রভাব ফেলবে না। আমি এটি অন্য ব্লগ পোস্টে ব্যাখ্যা করি, asyncএইচটিটিপি প্রোটোকল পরিবর্তন করে না
স্টিফেন ক্লিয়ারি

4
@ কিথ: asyncকোডবেস দিয়ে "বৃদ্ধি" করা স্বাভাবিক । যদি আপনার নিয়ামক পদ্ধতিটি অ্যাসিক্রোনাস অপারেশনের উপর নির্ভর করে, তবে বেস শ্রেণীর পদ্ধতিটি ফিরে আসা উচিতTask<ActionResult> । একটি বৃহত প্রকল্পে রূপান্তর asyncকরা সবসময় বিশ্রী কারণ মিক্স asyncএবং সিঙ্ক কোডটি কঠিন এবং কৌশলযুক্ত। খাঁটি asyncকোড অনেক সহজ।
স্টিফেন ক্লিয়ারি

12

আমি একই অচলাবস্থায় ছিলাম কিন্তু আমার ক্ষেত্রে একটি সিঙ্ক পদ্ধতি থেকে একটি অ্যাসিঙ্ক পদ্ধতিটি কল করা আমার পক্ষে কী কাজ করে তা ছিল:

private static SiteMetadataCacheItem GetCachedItem()
{
      TenantService TS = new TenantService(); // my service datacontext
      var CachedItem = Task.Run(async ()=> 
               await TS.GetTenantDataAsync(TenantIdValue)
      ).Result; // dont deadlock anymore
}

এটি কি ভাল ধারণা, কোনও ধারণা?


এই সমাধানটি আমার পক্ষেও কাজ করছে, তবে আমি নিশ্চিত না হয় এটি একটি ভাল সমাধান বা এটি কোথাও ভেঙে যেতে পারে। যে কেউ
এটিকে

ভালভাবে অবশেষে আমি এই সমাধানটি নিয়ে চলেছি এবং এটি সমস্যা ছাড়াই উত্পাদনশীল পরিবেশে কাজ করছে .....
ড্যানিলো

4
আমি মনে করি আপনি টাস্ক.রুন ব্যবহার করে একটি পারফরম্যান্স হিট করছেন। আমার পরীক্ষায় টাস্ক.আরুন 100 এমএমএস পোস্টের অনুরোধের জন্য কার্যকর করার সময়কে প্রায় দ্বিগুণ করছে।
টিমোথি গঞ্জালেজ


কল্পনাপ্রসূত এটি আমার পক্ষেও কাজ করেছিল, আমার কেসটিও একটি সিঙ্ক্রোনাস পদ্ধতিতে অ্যাসিক্রোনাসকে ডেকে আনা হয়েছিল। ধন্যবাদ!
লিওনার্দো স্পিনা

4

কেবল স্বীকৃত উত্তরের সাথে যুক্ত করতে (মন্তব্য করার মতো যথেষ্ট পরিমাণে প্রতিক্রিয়া নয়), এই সমস্যাটি যখন ব্যবহার করে অবরুদ্ধ করার সময় উত্থাপিত হয়েছিল task.Result, ঘটনাটির awaitনীচে প্রতিটি যদিও ConfigureAwait(false)এই উদাহরণটিতে রয়েছে:

public Foo GetFooSynchronous()
{
    var foo = new Foo();
    foo.Info = GetInfoAsync.Result;  // often deadlocks in ASP.NET
    return foo;
}

private async Task<string> GetInfoAsync()
{ 
    return await ExternalLibraryStringAsync().ConfigureAwait(false);
}

সমস্যাটি আসলে বাহ্যিক লাইব্রেরি কোডের সাথে থাকে। অ্যাসিঙ্ক লাইব্রেরি পদ্ধতি কলিং সিঙ্কের প্রসঙ্গে চালিয়ে যাওয়ার চেষ্টা করেছিল, আমি অপেক্ষা কীভাবে কনফিগার করেছি, তাতে অচলাবস্থার দিকে এগিয়ে যায়।

সুতরাং, উত্তরটি ছিল বাইরের লাইব্রেরি কোডটির নিজস্ব সংস্করণটি রোল করা ExternalLibraryStringAsync, যাতে এটির কাঙ্ক্ষিত ধারাবাহিকতা বৈশিষ্ট্য থাকে।


historicalতিহাসিক উদ্দেশ্যে ভুল উত্তর

অনেক ব্যথা এবং যন্ত্রণার পরে, আমি এই ব্লগ পোস্টে সমাধা সমাধানটি খুঁজে পেয়েছি ('অচলিত' জন্য Ctrl-f)। এটি task.ContinueWithখালি পরিবর্তে ব্যবহার করে চারদিকে ঘোরে task.Result

পূর্বে ডেডলকিং উদাহরণ:

public Foo GetFooSynchronous()
{
    var foo = new Foo();
    foo.Info = GetInfoAsync.Result;  // often deadlocks in ASP.NET
    return foo;
}

private async Task<string> GetInfoAsync()
{ 
    return await ExternalLibraryStringAsync().ConfigureAwait(false);
}

এইভাবে অচলাবস্থা এড়িয়ে চলুন:

public Foo GetFooSynchronous
{
    var foo = new Foo();
    GetInfoAsync()  // ContinueWith doesn't run until the task is complete
        .ContinueWith(task => foo.Info = task.Result);
    return foo;
}

private async Task<string> GetInfoAsync
{
    return await ExternalLibraryStringAsync().ConfigureAwait(false);
}

ডাউনটা কিসের জন্য? এই সমাধানটি আমার পক্ষে কাজ করছে।
ক্যামেরন জেফার্স

আপনি অবজেক্টটি পুনরায় সম্পন্ন হওয়ার আগেই ফিরিয়ে দিচ্ছেন Task, এবং কলারকে সরবরাহকারীর অবদানের পরিবর্তন কখন ঘটবে তা নির্ধারণের কোনও উপায় সরবরাহ করছেন না।
1915

হুঁ আমি দেখছি। সুতরাং আমি কি কোনও ধরণের "টাস্ক সম্পূর্ণ হওয়া পর্যন্ত অপেক্ষা করুন" পদ্ধতিটি প্রকাশ করতে পারি যা লুপের সময় ম্যানুয়ালি ব্লক করে (বা এরকম কিছু) ব্যবহার করে? বা GetFooSynchronousপদ্ধতিতে এই জাতীয় ব্লক প্যাক ?
ক্যামেরন জেফার্স

4
আপনি যদি করেন, এটি অচল হয়ে যাবে। আপনাকে Taskঅবরুদ্ধ করার পরিবর্তে ফিরে এসে পুরোপুরি অ্যাসিঙ্ক করা দরকার।
1915

দুর্ভাগ্যক্রমে এটি কোনও বিকল্প নয়, শ্রেণি একটি সিঙ্ক্রোনাস ইন্টারফেস প্রয়োগ করে যা আমি পরিবর্তন করতে পারি না।
ক্যামেরন জেফার্স

0

দ্রুত উত্তর: এই লাইনটি পরিবর্তন করুন

ResultClass slowTotal = asyncTask.Result;

প্রতি

ResultClass slowTotal = await asyncTask;

কেন? কনসোল অ্যাপ্লিকেশন ব্যতীত বেশিরভাগ অ্যাপ্লিকেশনগুলির মধ্যে কাজের ফলাফল পেতে আপনার .result ব্যবহার করা উচিত নয় যদি আপনি এটি করেন তবে আপনার প্রোগ্রামটি সেখানে পৌঁছে গেলে স্তব্ধ হয়ে যাবে

আপনি যদি ব্যবহার করতে চান তবে নীচের কোডটিও দেখতে পারেন। ফলাফল

ResultClass slowTotal = Task.Run(async ()=>await asyncTask).Result;
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.