كود - مشاريع جاهزة بلغة c#




كيفية استدعاء أسلوب غير متزامن من طريقة متزامنة في C#؟ (8)

async Main أصبح الآن جزءًا من C # 7.2 ويمكن تمكينه في إعدادات إنشاء المشاريع المتقدمة.

بالنسبة إلى C # <7.2 ، الطريقة الصحيحة هي:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

لدي طريقة public async void Foo() متزامنة عامًا أريد أن أتصل بها من طريقة متزامنة. حتى الآن ، كل ما رأيته من وثائق MSDN هو استدعاء أساليب التزامن عبر أساليب التزامن ، لكن برنامجي بالكامل لا يتم بناؤه باستخدام أساليب التزامن.

هل هذا ممكن؟

فيما يلي أحد الأمثلة على استدعاء هذه الطرق من طريقة غير متزامنة: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

الآن أنا أتطلع إلى استدعاء طرق التزامن هذه من طرق المزامنة.


إضافة الحل الذي حل مشكلتي في النهاية ، نأمل أن ينقذ وقت شخص ما.

قرأت أولاً مقالات عن ستيفن كليري :

من "أفضل ممارستين" في "عدم حظر على رمز التزامن" ، لم يعمل الأول من أجلي ولم يكن التطبيق الثاني قابلاً للتطبيق (أساسًا إذا كان يمكنني استخدام await ، أفعل!).

حتى هنا هو الحل الخاص بي: التفاف المكالمة داخل Task.Run<>(async () => await FunctionAsync()); وآمل أن لا طريق مسدود بعد الآن.

هنا الكود:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

الإجابة الأكثر قبولا ليست صحيحة تماما. هناك حل يعمل في كل موقف: مضخة رسالة مخصصة (SynchronizationContext).

سيتم حظر مؤشر ترابط الاستدعاءات كما هو متوقع ، مع الاستمرار في ضمان أن جميع الاستدعاءات التي يتم استدعاؤها من وظيفة التزامن لا تتوقف تمامًا حيث سيتم تنظيمها على SynchronizationContext المخصص (مضخة الرسالة) التي تعمل على مؤشر ترابط الاستدعاء.

رمز مساعد مضخة الرسالة المخصصة:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

الاستعمال:

AsyncPump.Run(() => FooAsync(...));

وصف أكثر تفصيلا لمضخة متزامن متاح here .


البرمجة غير المتزامنة "تنمو" خلال قاعدة التعليمات البرمجية. وقد قورنت بفيروس الزومبي . الحل الأفضل هو السماح لها بالنمو ، ولكن في بعض الأحيان هذا غير ممكن.

لقد كتبت بعض أنواع في مكتبة Nito.AsyncEx الخاصة بي للتعامل مع قاعدة التعليمات البرمجية غير المتزامنة جزئيا. لا يوجد حل يعمل في كل موقف ، مع ذلك.

الحل أ

إذا كان لديك طريقة بسيطة غير متزامنة لا تحتاج إلى المزامنة مرة أخرى إلى سياقها ، فيمكنك استخدام Task.WaitAndUnwrapException :

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

لا تريد استخدام Task.Wait أو Task.Result بسبب التفاف الاستثناءات في AggregateException .

هذا الحل مناسب فقط إذا لم يتزامن MyAsyncMethod مرة أخرى إلى السياق الخاص به. وبعبارة أخرى ، يجب أن ينتهي كل await في MyAsyncMethod مع ConfigureAwait(false) . هذا يعني أنه لا يمكن تحديث أي عناصر واجهة المستخدم أو الوصول إلى سياق طلب ASP.NET.

الحل ب

إذا MyAsyncMethod إلى المزامنة مرة أخرى إلى السياق الخاص به ، فقد تتمكن من استخدام AsyncContext.RunTask لتوفير سياق متداخل:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* التحديث 4/14/2014: في الإصدارات الأحدث من المكتبة ، تكون واجهة برمجة التطبيقات كما يلي:

var result = AsyncContext.Run(MyAsyncMethod);

(من الممكن استخدام Task.Result في هذا المثال لأن RunTask سيتم نشر استثناءات Task ).

السبب قد تحتاج AsyncContext.RunTask بدلاً من Task.WaitAndUnwrapException هو بسبب إمكانية deadlock خفية إلى حد ما يحدث على WinForms / WPF / SL / ASP.NET:

  1. يستدعي أسلوب متزامن طريقة متزامن ، الحصول على Task .
  2. الأسلوب المتزامن يقوم بحظر حظر على Task .
  3. يستخدم أسلوب async دون ConfigureAwait .
  4. لا يمكن إكمال Task في هذا الموقف لأنه اكتمال فقط عند انتهاء الأسلوب async ؛ لا يمكن إكمال أسلوب async لأنه يحاول جدولة استمراره إلى SynchronizationContext ، ولن يسمح WinForms / WPF / SL / ASP.NET استمرار تشغيل لأن أسلوب التزامن قيد التشغيل بالفعل في هذا السياق.

هذا هو أحد الأسباب التي تجعل من الأفضل استخدام ConfigureAwait(false) داخل كل أسلوب غير async قدر الإمكان.

الحل C

لن يعمل AsyncContext.RunTask في كل سيناريو. على سبيل المثال ، إذا كانت طريقة async تنتظر شيئًا يتطلب استكمال حدث واجهة المستخدم ، فستتلقى حالة توقف تام حتى مع السياق المتداخل. في هذه الحالة ، يمكنك بدء تشغيل الأسلوب async على تجمع async الترابط:

var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

ومع ذلك ، يتطلب هذا الحل MyAsyncMethod التي ستعمل في سياق تجمع MyAsyncMethod الترابط. لذلك لا يمكن تحديث عناصر واجهة المستخدم أو الوصول إلى سياق طلب ASP.NET. وفي هذه الحالة ، يمكنك إضافة ConfigureAwait(false) إلى عبارات await الخاصة به ، واستخدام الحل A.


لست متأكدًا بنسبة 100٪ ، لكنني أعتقد أن التقنية الموضحة في هذه المدونة يجب أن تعمل في العديد من الظروف:

يمكنك بالتالي استخدام task.GetAwaiter().GetResult() إذا كنت تريد استدعاء منطق task.GetAwaiter().GetResult() هذا مباشرةً.


هذه الأساليب المتزامن windows لها أسلوب nifty قليلاً تسمى AsTask (). يمكنك استخدام هذا الأسلوب لإرجاع نفسه كمهمة بحيث يمكنك الاتصال يدوياً Wait () عليه.

على سبيل المثال ، في تطبيق Windows Phone 8 Silverlight ، يمكنك القيام بما يلي:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

أتمنى أن يساعدك هذا!


   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

تقرأ الكلمة الأساسية "تنتظر" على أنها "بدء هذه المهمة الطويلة الأمد ، ثم إرجاع التحكم إلى طريقة الاستدعاء". بمجرد الانتهاء من المهمة التي يتم تشغيلها منذ فترة طويلة ، فإنها تنفذ الرمز بعدها. يشبه الكود بعد الانتظار إلى ما كان عليه في السابق طرق CallBack. الفارق الكبير هو أن التدفق المنطقي لا ينقطع مما يجعل من الأسهل الكتابة والقراءة.





async-await