c# - HttpClient.GetAsync(…) عدم إرجاع عند استخدام outait/async




.net asynchronous (4)

تعديل: يبدو أن هذا السؤال قد يكون نفس المشكلة ، ولكن ليس لديه ردود ...

تحرير: في حالة اختبار 5 يبدو أن المهمة عالقة في حالة WaitingForActivation .

لقد واجهت بعض السلوك الغريب باستخدام System.Net.Http.HttpClient في .NET 4.5 - حيث "تنتظر" نتيجة استدعاء (على سبيل المثال) httpClient.GetAsync(...) بالعودة.

يحدث هذا فقط في ظروف معينة عند استخدام وظائف اللغة الجديدة غير المتزامنة / المنتظرة وواجهة برمجة التطبيقات لـ Tasks - يبدو أن الشفرة تعمل دائمًا عند استخدام الاستمرارية فقط.

إليك بعض التعليمات البرمجية التي تعيد إنتاج المشكلة - قم بإسقاط هذا في "مشروع WebApi 4 MVC 4" جديد في Visual Studio 11 لعرض نقاط النهاية التالية لـ GET:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

تقوم كل نقطة من نقاط النهاية هنا بإرجاع نفس البيانات (رؤوس الاستجابة من stackoverflow.com) باستثناء /api/test5 التي لا تكتمل أبدًا.

هل واجهت مشكلة في فئة HttpClient ، أو هل أساء استخدام واجهة برمجة التطبيقات بطريقة ما؟

رمز إعادة إنتاج:

public class BaseApiController : ApiController
{
    /// <summary>
    /// Retrieves data using continuations
    /// </summary>
    protected Task<string> Continuations_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
    }

    /// <summary>
    /// Retrieves data using async/await
    /// </summary>
    protected async Task<string> AsyncAwait_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return result.Content.Headers.ToString();
    }
}

public class Test1Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await Continuations_GetSomeDataAsync();

        return data;
    }
}

public class Test2Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = Continuations_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test3Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return Continuations_GetSomeDataAsync();
    }
}

public class Test4Controller : BaseApiController
{
    /// <summary>
    /// Handles task using Async/Await
    /// </summary>
    public async Task<string> Get()
    {
        var data = await AsyncAwait_GetSomeDataAsync();

        return data;
    }
}

public class Test5Controller : BaseApiController
{
    /// <summary>
    /// Handles task by blocking the thread until the task completes
    /// </summary>
    public string Get()
    {
        var task = AsyncAwait_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test6Controller : BaseApiController
{
    /// <summary>
    /// Passes the task back to the controller host
    /// </summary>
    public Task<string> Get()
    {
        return AsyncAwait_GetSomeDataAsync();
    }
}

أنا أتطلع هنا:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx

و هنا:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

ورؤية:

هذا النوع وأعضائه مخصصة للاستخدام من قبل المترجم.

بالنظر إلى await إصدار النسخة ، وهي الطريقة "الصحيحة" لفعل الأشياء ، هل تحتاج حقًا إلى إجابة على هذا السؤال؟

تصويتي هو: إساءة استخدام واجهة برمجة التطبيقات .


أنت تسيء استخدام API.

ها هو الموقف: في ASP.NET ، يمكن لموضوع واحد فقط معالجة طلب في كل مرة. يمكنك إجراء بعض المعالجة المتوازية إذا لزم الأمر (استدراج مؤشرات ترابط إضافية من تجمع مؤشرات الترابط) ، ولكن مؤشر ترابط واحد فقط سيكون له سياق الطلب (لا تحتوي سلاسل العمليات الإضافية على سياق الطلب).

يتم إدارتها بواسطة ASP.NET SynchronizationContext .

بشكل افتراضي ، عندما await Task ، يستأنف الأسلوب على SynchronizationContext الملتقطة (أو TaskScheduler الملتقطة ، إذا لم يكن هناك SynchronizationContext ). عادةً ما يكون هذا هو ما تريده: إجراء وحدة تحكم غير متزامن سوف await شيء ما ، وعندما يستأنف ، يستأنف مع سياق الطلب.

إذن ، لماذا فشل test5 :

  • Test5Controller.Get ينفذ AsyncAwait_GetSomeDataAsync (ضمن سياق طلب ASP.NET).
  • تنفيذ AsyncAwait_GetSomeDataAsync HttpClient.GetAsync (ضمن سياق طلب ASP.NET).
  • يتم إرسال طلب HTTP ، ويعيد HttpClient.GetAsync Task غير كاملة.
  • AsyncAwait_GetSomeDataAsync ينتظر Task ؛ نظرًا لعدم اكتماله ، يقوم AsyncAwait_GetSomeDataAsync بإرجاع Task غير كاملة.
  • Test5Controller.Get كتل الصفحات الحالية حتى تكمل Task .
  • تأتي استجابة HTTP في ، وتكتمل Task التي تم إرجاعها بواسطة HttpClient.GetAsync .
  • يحاول AsyncAwait_GetSomeDataAsync استئناف ضمن سياق طلب ASP.NET. ومع ذلك ، يوجد بالفعل مؤشر ترابط في هذا السياق: تم حظر مؤشر الترابط في Test5Controller.Get .
  • طريق مسدود.

إليك سبب عمل الآخرين:

  • ( test1 و test2 و test3 ): Continuations_GetSomeDataAsync بجدولة الاستمرارية إلى تجمع مؤشرات الترابط ، خارج سياق طلب ASP.NET. هذا يسمح Task التي تم إرجاعها بواسطة Continuations_GetSomeDataAsync لإكمال دون الحاجة إلى إعادة إدخال سياق الطلب.
  • ( test4 و test6 ): حيث يتم انتظار Task ، لا يتم حظر مؤشر ترابط طلب ASP.NET. هذا يسمح AsyncAwait_GetSomeDataAsync لاستخدام سياق طلب ASP.NET عندما يكون جاهزاً للمتابعة.

وإليك أفضل الممارسات:

  1. في طرق async "library" الخاصة بك ، استخدم ConfigureAwait(false) كلما أمكن ذلك. في هذه الحالة ، سيؤدي هذا إلى تغيير AsyncAwait_GetSomeDataAsync إلى أن يكون var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. لا تحظر Task s؛ انها async على طول الطريق. بمعنى آخر ، استخدام await بدلاً من GetResult ( Task.Result و Task.Wait يجب أيضاً استبداله await ).

بهذه الطريقة ، يمكنك الحصول على كلا الفوائد: يتم تشغيل الاستمرارية (ما تبقى من أسلوب AsyncAwait_GetSomeDataAsync ) على مؤشر ترابط تجمّع مؤشر ترابط أساسي لا يلزم إدخال سياق طلب ASP.NET؛ ووحدة التحكم نفسها هي async (التي لا تحظر مؤشر ترابط الطلب).

معلومات اكثر:

التحديث 2012-07-13: تم دمج هذه الإجابة في مشاركة مدونة .


نظرًا لأنك تستخدم .Result أو. .Wait أو await سيؤدي ذلك إلى حدوث حالة توقف تام في التعليمات البرمجية.

يمكنك استخدام ConfigureAwait(false) في أساليب async لمنع حالة توقف تام

مثله:

var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);

يمكنك استخدام ConfigureAwait(false) حيثما أمكن لـ عدم حظر رمز Async.


هاتان المدرستان لا يستبعدان حقًا.

هنا هو السيناريو حيث عليك ببساطة استخدام

   Task.Run(() => AsyncOperation()).Wait(); 

أو شيء من هذا القبيل

   AsyncContext.Run(AsyncOperation);

لدي إجراء MVC ضمن سمة معاملة قاعدة البيانات. كانت الفكرة (على الأرجح) تتراجع عن كل شيء تم تنفيذه في الإجراء إذا حدث خطأ ما. هذا لا يسمح بتغيير السياق ، وإلا ستفشل عملية التراجع عن التشغيل أو الالتزام.

المكتبة التي أحتاج إليها هي غير متزامن حيث من المتوقع تشغيلها بشكل متزامن.

الخيار الوحيد. قم بتشغيله كمكالمة مزامنة عادية.

أنا فقط أقول لكل منها.





dotnet-httpclient