ฉันจะใช้วิธีการทำงานของ async <T> แบบซิงโครนัสได้อย่างไร


628

ฉันเรียนรู้เกี่ยวกับ async / คอยและพบกับสถานการณ์ที่ฉันต้องเรียกวิธีการ async พร้อมกัน ฉันจะทำสิ่งนั้นได้อย่างไร

วิธีการซิงค์:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

การใช้งานปกติ:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

ฉันได้ลองใช้สิ่งต่อไปนี้:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

ฉันลองใช้คำแนะนำจากที่นี่แต่มันไม่ทำงานเมื่อโปรแกรมเลือกจ่ายงานอยู่ในสถานะถูกระงับ

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

นี่คือข้อยกเว้นและการติดตามสแต็กจากการโทรRunSynchronously:

System.InvalidOperationException

ข้อความ : RunSynchronously อาจไม่ถูกเรียกบนภารกิจที่ไม่ได้ผูกไว้กับผู้รับมอบสิทธิ์

InnerException : null

ที่มา : mscorlib

StackTrace :

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

46
คำตอบที่ดีที่สุดสำหรับคำถาม "ฉันจะเรียกวิธีการแบบอะซิงโครนัสพร้อมกันได้อย่างไร" คือ "ไม่" มีแฮ็กที่จะพยายามบังคับให้มันทำงานได้ แต่พวกมันทั้งหมดมีข้อผิดพลาดเล็กน้อย ให้สำรองและแก้ไขรหัสที่ทำให้คุณ "ต้อง" แทน
Stephen Cleary

57
@Stephen Cleary เห็นด้วยอย่างแน่นอน แต่บางครั้งมันก็หลีกเลี่ยงไม่ได้เช่นเมื่อรหัสของคุณขึ้นอยู่กับ API บุคคลที่สามที่ไม่ได้ใช้ async / คอย นอกจากนี้หากเชื่อมโยงกับคุณสมบัติ WPF เมื่อใช้ MVVM เป็นไปไม่ได้ที่จะใช้ async / await เนื่องจากไม่รองรับคุณสมบัติ
Contango

3
@StephenCleary ไม่เสมอไป ฉันสร้าง DLL ซึ่งจะถูกนำเข้ามาในGenexus ไม่รองรับคำหลักแบบ async / รอดังนั้นฉันต้องใช้วิธีการแบบซิงโครนัสเท่านั้น
Dinei

5
@StephenCleary 1) GeneXus เป็นเครื่องมือ pt ตัวที่ 3 และฉันไม่มีสิทธิ์เข้าถึงซอร์สโค้ด 2) GeneXus ไม่มีแม้กระทั่งการใช้ "ฟังก์ชั่น" ดังนั้นฉันจึงไม่สามารถรู้ได้ว่าจะใช้ "โทรกลับ" กับสิ่งประเภทนี้ได้อย่างไร แน่นอนว่ามันจะเป็นการแก้ปัญหาที่ยากกว่าการใช้แบบTaskซิงโครนัส 3) ฉันรวม GeneXus เข้ากับไดรเวอร์ MongoDB C #ซึ่งแสดงวิธีการบางอย่างแบบอะซิงโครนัสเท่านั้น
Dinei

1
@ygoe: ใช้ล็อค async SemaphoreSlimที่เข้ากันได้เช่น
Stephen Cleary

คำตอบ:


456

นี่เป็นวิธีแก้ปัญหาที่ฉันพบว่าใช้งานได้กับทุกกรณี (รวมถึงโปรแกรมเลือกจ่ายงานที่ถูกระงับ) ไม่ใช่รหัสของฉันและฉันยังคงทำงานเพื่อทำความเข้าใจ แต่ก็ใช้งานได้

มันสามารถเรียกได้ว่าใช้:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

รหัสมาจากที่นี่

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

28
สำหรับพื้นหลังเกี่ยวกับวิธีการทำงานสตีเฟ่น Toub (Mr Parallel) เขียนชุดของโพสต์เกี่ยวกับเรื่องนี้ ส่วนที่ 1 ส่วนที่ 2 ส่วนที่ 3
Cameron MacFarland

18
ฉันปรับปรุงรหัสของจอห์นในการทำงานโดยไม่ต้องตัดงานใน lambdas: github.com/tejacques/AsyncBridge โดยพื้นฐานแล้วคุณทำงานกับบล็อก async ด้วยคำสั่ง using สิ่งใดก็ตามในบล็อกที่ใช้จะเกิดขึ้นแบบอะซิงโครนัสโดยมีการรอที่ส่วนท้าย ข้อเสียคือคุณต้องแกะงานด้วยตัวเองในการโทรกลับ แต่มันก็ยังค่อนข้างหรูหราโดยเฉพาะอย่างยิ่งถ้าคุณต้องการเรียกฟังก์ชั่น async หลายอย่างพร้อมกัน
Tom Jacques

17
@StephenCleary แม้ว่าฉันมักจะเห็นด้วยกับคุณว่ารหัสควรจะ async ตลอดทางลงบางครั้งคุณพบว่าตัวเองอยู่ในสถานการณ์ที่เป็นไปไม่ได้ที่หนึ่งมีการบังคับให้มันเป็นสายซิงโคร โดยทั่วไปสถานการณ์ของฉันคือรหัสการเข้าถึงข้อมูลทั้งหมดของฉันอยู่ในรูปแบบ async ฉันต้องการสร้างแผนผังเว็บไซต์ตาม sitemap และห้องสมุดบุคคลที่สามที่ฉันใช้คือ MvcSitemap ตอนนี้เมื่อมีการขยายผ่านDynamicNodeProviderBaseชั้นฐานหนึ่งไม่สามารถประกาศว่าเป็นasyncวิธีการ ฉันต้องแทนที่ด้วยไลบรารีใหม่หรือเพียงแค่เรียก op แบบซิงโครนัส
justin.lovell

6
@ justin.lovell: ใช่ข้อ จำกัด ของไลบรารีสามารถบังคับให้เราใส่แฮ็กอย่างน้อยก็จนกว่าจะมีการปรับปรุงห้องสมุด ดูเหมือนว่า MvcSitemap เป็นหนึ่งในสถานการณ์ที่ต้องการแฮ็ค (ตัวกรอง MVC และการกระทำของลูกด้วย) ฉันแค่ห้ามผู้คนจากเรื่องนี้โดยทั่วไปเพราะแฮ็กแบบนี้ถูกใช้บ่อยเกินไปเมื่อไม่จำเป็น ด้วย MVC โดยเฉพาะ ASP.NET/MVC API บางตัวจะถือว่าพวกเขามีAspNetSynchronizationContextดังนั้นแฮ็คนี้จะไม่ทำงานหากคุณเรียก API เหล่านั้น
สตีเฟ่นเคลียร์

5
รหัสนี้จะไม่ทำงาน หากถูกเรียกจากเธรดพูลสามารถทำให้เกิดการหยุดชะงักของเธรดการขาดอาหาร ผู้เรียกของคุณจะปิดกั้นการรอให้การดำเนินการเสร็จสิ้นซึ่งอาจไม่เกิดขึ้นหากเขาใช้พูลเธรดจนหมด ดูบทความนี้
ZunTzu

318

ได้รับคำแนะนำคำตอบนี้มีอายุสามปี ฉันเขียนมันขึ้นอยู่กับประสบการณ์ที่มีสุทธิ 4.0 และน้อยมากกับ 4.5 async-awaitโดยเฉพาะอย่างยิ่งกับ โดยทั่วไปการพูดมันเป็นวิธีการแก้ปัญหาที่ดี แต่บางครั้งก็อาจแบ่งสิ่งต่าง ๆ ได้ โปรดอ่านการอภิปรายในความคิดเห็น

.Net 4.5

เพียงใช้สิ่งนี้:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

โปรดดู: TaskAwaiter , Task.Result , Task.RunSynchronously


.Net 4.0

ใช้สิ่งนี้:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

... หรือสิ่งนี้:

task.Start();
task.Wait();

67
.Resultสามารถสร้างการหยุดชะงักในบางสถานการณ์
Jordy Langen

122
Resultสามารถทำให้เกิดการหยุดชะงักในasyncรหัสได้อย่างง่ายดายตามที่ฉันอธิบายในบล็อกของฉัน
Stephen Cleary

8
@StephenCleary ฉันอ่านโพสต์ของคุณและลองด้วยตัวเอง ฉันคิดว่าคนที่ไมโครซอฟท์เมาจริงๆ ... มันเป็นปัญหาเช่นเดียวกับ winforms และเธรดพื้นหลัง ....
AK_

9
คำถามเกี่ยวข้องกับภารกิจที่ส่งคืนโดยเมธอด async เช่นชนิดของงานอาจได้รับการเริ่มต้นดำเนินการหรือยกเลิกเพื่อให้การใช้งานของTask.RunSynchronouslyวิธีการอาจส่งผลให้InvalidOperationException ดูหน้า MSDN: วิธี Task.RunSynchronously นอกจากนี้งานนั้นอาจถูกสร้างขึ้นโดยTask.Factory.StartNewหรือวิธีTask.Run (ภายในวิธีการ async) ดังนั้นจึงเป็นอันตรายที่จะลองเริ่มใหม่อีกครั้ง สภาพการแข่งขันบางอย่างอาจเกิดขึ้นในขณะทำงาน ในมืออื่น ๆTask.WaitและTask.Resultอาจส่งผลให้ฉันหยุดชะงัก
sgnsajgon

4
เรียกใช้การทำงานแบบซิงโครนัสสำหรับฉัน ... ฉันไม่รู้ว่าฉันขาดอะไรไปบ้างหรือเปล่า ui จากการแขวน
JonnyRaa

121

แปลกใจที่ไม่มีใครพูดถึงเรื่องนี้:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

ไม่สวยเท่าวิธีอื่น ๆ ที่นี่ แต่มีประโยชน์ดังต่อไปนี้:

  • มันไม่กลืนข้อยกเว้น (เช่นWait)
  • มันจะไม่ห่อข้อยกเว้นใด ๆ ที่ถูกโยนในAggregateException(เช่นResult)
  • ทำงานได้ทั้งTaskและTask<T>( ลองด้วยตัวเอง! )

นอกจากนี้เนื่องจากGetAwaiterถูกพิมพ์ด้วยเป็ดสิ่งนี้ควรทำงานกับวัตถุใด ๆ ที่ส่งคืนจากเมธอด async (เหมือนConfiguredAwaitableหรือYieldAwaitable) ไม่ใช่แค่งาน


แก้ไข:โปรดทราบว่าเป็นไปได้สำหรับวิธีการนี้ (หรือใช้.Result) ในการหยุดชะงักเว้นแต่ว่าคุณจะต้องเพิ่ม.ConfigureAwait(false)ทุกครั้งที่คุณรอสำหรับวิธีการซิงค์ทั้งหมดที่สามารถเข้าถึงได้BlahAsync()(ไม่ใช่แค่การโทรโดยตรง) คำอธิบาย

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

หากคุณขี้เกียจเกินกว่าที่จะเพิ่มได้.ConfigureAwait(false)ทุกที่และคุณไม่สนใจเกี่ยวกับประสิทธิภาพที่คุณสามารถทำได้

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

1
เหมาะกับฉันสำหรับสิ่งที่ง่าย นอกจากนี้หากวิธีการส่งกลับ IAsyncOperation ฉันต้องแปลงเป็นงานแรก: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Lee McPherson

3
สิ่งนี้ทำให้เกิดการหยุดชะงักภายในเมธอด asmx เว็บ อย่างไรก็ตามการตัดการเรียกเมธอดใน Task.Run () ทำให้ทำงาน: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Augusto Barreto

ฉันชอบวิธีนี้ดีที่สุด syntactically เพราะมันไม่เกี่ยวข้องกับ lambdas
dythim

25
โปรดอย่าแก้ไขคำตอบของผู้อื่นเพื่อแทรกลิงค์ของคุณเอง หากคุณเชื่อว่าคำตอบของคุณดีกว่าให้ทิ้งไว้เป็นความคิดเห็นแทน
Rachel

1
docs.microsoft.com/en-us/dotnet/api/…พูดถึงGetAwaiter()"วิธีนี้มีไว้สำหรับผู้ใช้คอมไพเลอร์แทนที่จะใช้ในรหัสโดยตรง"
Theophilus

75

มันง่ายกว่ามากในการรันภารกิจบนเธรดพูลแทนที่จะพยายามหลอกให้ตัวกำหนดตารางเวลารันแบบซิงโครนัส ด้วยวิธีนี้คุณสามารถมั่นใจได้ว่ามันจะไม่หยุดชะงัก ประสิทธิภาพได้รับผลกระทบเนื่องจากการสลับบริบท

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

3
จากนั้นคุณเรียก task.Wait () ประเภทข้อมูลเป็นเพียงงาน
ไมเคิลเพอร์รี่ L

1
สมมติว่าDoSomethingAsync ()เป็นวิธี async ที่ใช้เวลานานโดยรวม (ภายในจะรองานที่รันนาน) แต่มันให้ผลตอบแทนการควบคุมการไหลกลับไปยังผู้เรียกอย่างรวดเร็ว ผลลัพธ์ของ Tusk.Run () อาจTask <Task>หรือTask <Task <>>ดังนั้นคุณกำลังรอผลลัพธ์ของงานภายนอกที่เสร็จสมบูรณ์อย่างรวดเร็ว แต่งานภายใน (เนื่องจากรองานที่ยาวนานในวิธี async) ยังคงทำงานอยู่ ข้อสรุปคือเราอาจต้องใช้วิธี Unwrap () (ตามที่ทำใน @ J.Lennon post) เพื่อให้ได้พฤติกรรมแบบซิงโครนัสของวิธีการแบบอะซิงโครนัส
sgnsajgon

5
@sgnsajgon คุณคิดผิด Task.Run นั้นแตกต่างจาก Task.Fartory.StartNew โดยที่มันจะทำการแยกผลลัพธ์ออกมาโดยอัตโนมัติ ดูบทความนี้
ซุนตู

1
ฉันเขียนTask.Run(DoSomethingAsync)แทนได้ไหม สิ่งนี้จะลบผู้รับมอบสิทธิ์หนึ่งระดับ
ygoe

1
อ๋อ แม้ว่าจะไปในทิศทางตรงกันข้ามเช่นเดียวกับในที่Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());ชัดเจนมากขึ้นและจัดการข้อกังวลโดย @sgnsajgon ว่าอาจส่งคืนภารกิจ <Task <MyResult>> การโอเวอร์โหลดที่ถูกต้องของ Task.Run นั้นถูกเลือกด้วยวิธีใดวิธีหนึ่ง แต่ผู้รับมอบสิทธิ์ async ทำให้ความตั้งใจของคุณชัดเจน
Michael L Perry

57

ฉันเรียนรู้เกี่ยวกับ async / คอยและพบกับสถานการณ์ที่ฉันต้องเรียกวิธีการ async พร้อมกัน ฉันจะทำสิ่งนั้นได้อย่างไร

คำตอบที่ดีที่สุดคือคุณทำไม่ได้โดยมีรายละเอียดขึ้นอยู่กับว่า "สถานการณ์" คืออะไร

มันเป็นคุณสมบัติทะเยอทะยาน / setter? ในกรณีส่วนใหญ่จะดีกว่าถ้ามีวิธีแบบอะซิงโครนัสมากกว่า "คุณสมบัติแบบอะซิงโครนัส" (สำหรับข้อมูลเพิ่มเติมดูโพสต์บล็อกของฉันเกี่ยวกับคุณสมบัติแบบอะซิงโครนัส )

นี่เป็นแอป MVVM และคุณต้องการผูกข้อมูลแบบอะซิงโครนัสหรือไม่ แล้วใช้สิ่งที่ต้องการของฉันNotifyTaskที่อธิบายไว้ในของฉันบทความ MSDN กับข้อมูลที่ไม่ตรงกันมีผลผูกพัน

มันเป็นตัวสร้างหรือไม่? จากนั้นคุณอาจต้องการพิจารณาวิธีโรงงานแบบอะซิงโครนัส (สำหรับข้อมูลเพิ่มเติมดูโพสต์บล็อกของฉันในตัวสร้างแบบอะซิงโครนัส )

มีคำตอบที่ดีกว่าการซิงค์แบบอะซิงโครนัสเกือบทุกครั้ง

หากเป็นไปไม่ได้สำหรับสถานการณ์ของคุณ (และคุณรู้เรื่องนี้โดยถามคำถามที่นี่เพื่ออธิบายสถานการณ์ ) ดังนั้นฉันขอแนะนำให้ใช้รหัสซิงโครนัส Async ตลอดทางดีที่สุด ซิงค์ทุกวิธีดีที่สุดเป็นอันดับสอง ไม่แนะนำให้ซิงค์กับ over-async

อย่างไรก็ตามมีสถานการณ์เพียงไม่กี่อย่างที่จำเป็นต้องใช้ โดยเฉพาะอย่างยิ่งคุณถูก จำกัด โดยรหัสการโทรเพื่อให้คุณต้องซิงค์ (และไม่มีวิธีคิดหรือจัดโครงสร้างรหัสของคุณใหม่เพื่อให้ไม่ตรงกัน) และคุณต้องเรียกรหัส async อันนี้เป็นอย่างมากสถานการณ์ที่หายากแต่มันก็เกิดขึ้นเป็นครั้งคราว

ในกรณีนี้คุณจะต้องใช้หนึ่งในแฮ็กที่อธิบายไว้ในบทความของฉันเกี่ยวกับการพัฒนาBrownfieldasyncโดยเฉพาะ:

  • การปิดกั้น (เช่นGetAwaiter().GetResult()) โปรดทราบว่านี่อาจทำให้เกิดการหยุดชะงัก (ตามที่ฉันอธิบายในบล็อกของฉัน)
  • การเรียกใช้โค้ดบนเธรดพูลเธรด (เช่นTask.Run(..).GetAwaiter().GetResult()) โปรดทราบว่านี่จะใช้งานได้หากรหัสอะซิงโครนัสสามารถรันได้บนเธรดพูลเธรด (เช่นไม่ได้ขึ้นอยู่กับบริบท UI หรือ ASP.NET)
  • ข้อความที่ซ้อนกันวนซ้ำ โปรดทราบว่านี่จะใช้งานได้หากรหัสอะซิงโครนัสถือว่าเป็นบริบทแบบเธรดเดียวไม่ใช่ประเภทบริบทเฉพาะ (UI และรหัส ASP.NET จำนวนมากคาดหวังบริบทเฉพาะ)

ลูปข้อความที่ซ้อนกันเป็นที่อันตรายที่สุดของแฮ็กทั้งหมดเพราะมันทำให้เกิดอีกครั้ง entrancy การเข้ามาใหม่นั้นยากมากที่จะให้เหตุผลและ (IMO) เป็นสาเหตุของข้อบกพร่องของแอปพลิเคชันส่วนใหญ่ใน Windows โดยเฉพาะอย่างยิ่งถ้าคุณอยู่ในเธรด UI และคุณบล็อกคิวงาน (รอให้การทำงานของ async เสร็จสมบูรณ์) CLR จะส่งข้อความถึงคุณจริง ๆ - มันจะจัดการกับข้อความ Win32 บางส่วนจากภายในของคุณ รหัส โอ้และคุณไม่รู้หรอกว่าข้อความอะไร - เมื่อคริสบรูม พูดว่า "มันจะไม่เป็นการดีที่จะรู้ว่าสิ่งที่จะได้รับการสูบฉีด แต่น่าเสียดายที่การปั๊มเป็นศิลปะสีดำซึ่งเกินกว่าความเข้าใจของมนุษย์"แล้วเราก็ไม่มีความหวังที่จะรู้

ดังนั้นเมื่อคุณบล็อกสิ่งนี้ในเธรด UI คุณจะถามถึงปัญหา คำพูด cbrumme อื่นจากบทความเดียวกัน: "ในบางครั้งลูกค้าภายในหรือภายนอก บริษัท ค้นพบว่าเรากำลังปั๊มข้อความในระหว่างการบล็อกการจัดการบน STA [UI thread] นี่เป็นข้อกังวลที่ถูกต้องตามกฎหมายเพราะพวกเขารู้ว่ามันยากมาก เพื่อเขียนโค้ดที่มีความแข็งแกร่งเมื่อเผชิญกับ reentrancy "

ใช่แล้ว. ยากมากที่จะเขียนโค้ดที่มีความแข็งแกร่งเมื่อเผชิญกับความ reentrancy และลูปข้อความซ้อนกันบังคับให้คุณเขียนโค้ดที่มีความแข็งแกร่งเมื่อเผชิญกับการแสดงซ้ำ นี่คือเหตุผลที่คำตอบที่ยอมรับ (และถูกโหวตมากที่สุด) สำหรับคำถามนี้เป็นอันตรายอย่างยิ่งในทางปฏิบัติ

หากคุณไม่มีตัวเลือกอื่น ๆ ทั้งหมด - คุณไม่สามารถออกแบบรหัสของคุณใหม่คุณจะไม่สามารถปรับโครงสร้างให้เป็นแบบอะซิงก์ได้ - คุณจะถูกบังคับให้ซิงค์รหัสที่ไม่สามารถเปลี่ยนได้เพื่อซิงค์คุณไม่สามารถเปลี่ยนรหัสดาวน์สตรีมให้ซิงค์ได้ - คุณไม่สามารถบล็อกได้ - คุณไม่สามารถเรียกใช้รหัส async บนเธรดแยกกัน - จากนั้นคุณควรพิจารณาใช้การอ้างอิงซ้ำ

หากคุณพบว่าตัวเองอยู่ในมุมนี้ผมจะแนะนำให้ใช้สิ่งที่ต้องการDispatcher.PushFrameสำหรับแอป WPF , บ่วงกับApplication.DoEventsปพลิเคชัน WinForm AsyncContext.Runและสำหรับกรณีทั่วไปของตัวเอง


Stephen มีอีกคำถามที่คล้ายกันซึ่งคุณได้ให้คำตอบที่ยอดเยี่ยมเช่นกัน คุณคิดว่าหนึ่งในพวกเขาสามารถปิดเป็นซ้ำหรืออาจรวมหรือขอเมตาก่อน (ตามที่แต่ละคิวมี ~ 200K views 200+ คะแนนโหวต)? ข้อเสนอแนะ?
Alexei Levenkov

1
@AlexeiLevenkov: ฉันรู้สึกไม่ถูกต้องด้วยเหตุผลสองสามประการ: 1) คำตอบสำหรับคำถามที่เชื่อมโยงนั้นล้าสมัยแล้ว 2) ฉันได้เขียนบทความทั้งหมดเกี่ยวกับเรื่องที่ฉันรู้สึกว่าสมบูรณ์มากกว่า SO Q / A ที่มีอยู่ 3) คำตอบที่ได้รับการยอมรับสำหรับคำถามนี้เป็นที่นิยมอย่างมาก 4) ฉันไม่เห็นด้วยอย่างรุนแรงกับคำตอบที่ยอมรับ ดังนั้นการปิดเป็นสิ่งที่ซ้ำซ้อนอาจเป็นการใช้อำนาจในทางที่ผิด; การปิดที่เป็นส่วนหนึ่งของ (หรือการรวม) นี้จะช่วยให้คำตอบที่เป็นอันตรายมากยิ่งขึ้น ฉันปล่อยให้มันเป็นและปล่อยให้ชุมชน
Stephen Cleary

ตกลง. ฉันจะลองพิจารณาเมตาดาต้ามากกว่าที่คิด
Alexei Levenkov

9
คำตอบนี้ไปไกลกว่าหัวของฉัน "ใช้ async จนสุดความสามารถ"ทำให้เกิดความสับสนเนื่องจากคำแนะนำไม่สามารถติดตามได้อย่างชัดเจน โปรแกรมที่มีMain()เมธอดasync ไม่ได้รวบรวม ในบางจุดที่คุณเคยได้เพื่อลดช่องว่างระหว่างการซิงค์และโลก async มันไม่ได้เป็น" มากสถานการณ์ยาก"ก็จำเป็นในตัวอักษรทุกโปรแกรมที่เรียกวิธี async ไม่มีตัวเลือกที่จะไม่"ทำการซิงค์แบบ over-async"เพียงตัวเลือกที่จะแบ่งภาระให้กับวิธีการโทรแทนการ shouldering ในสิ่งที่คุณกำลังเขียน
Mark Amery

1
ยิ่งใหญ่ ฉันกำลังจะใส่asyncวิธีการทั้งหมดในใบสมัครของฉันตอนนี้ และนั่นเป็นจำนวนมาก นี่เป็นเพียงค่าเริ่มต้นใช่ไหม
ygoe

25

หากฉันกำลังอ่านคำถามของคุณถูกต้อง - รหัสที่ต้องการให้การเรียกแบบซิงโครนัสไปยังวิธีการแบบอะซิงโครนัสกำลังทำงานบนเธรดโปรแกรมเลือกจ่ายงานที่ถูกระงับ และคุณต้องการบล็อกเธรดนั้นพร้อมกันจนกว่าวิธีการ async จะเสร็จสมบูรณ์

วิธีการแบบอะซิงก์ใน C # 5 นั้นขับเคลื่อนโดยการสับอย่างมีประสิทธิภาพเป็นชิ้น ๆ ภายใต้ประทุนและการส่งกลับTaskที่สามารถติดตามความสำเร็จโดยรวมของ shabang ทั้งหมด อย่างไรก็ตามวิธีการที่สับดำเนินการสามารถขึ้นอยู่กับชนิดของนิพจน์ที่ส่งผ่านไปยังawaitผู้ประกอบการ

ส่วนใหญ่เวลาที่คุณจะใช้ในการแสดงออกของพิมพ์await Taskการใช้งานของawaitรูปแบบคือ "สมาร์ท" ในการที่จะเลื่อนไปที่SynchronizationContextซึ่งทำให้เกิดความซึ่งโดยทั่วไปจะทำให้เกิดสิ่งต่อไปนี้:

  1. หากด้ายเข้ามา awaitอยู่ใน Dispatcher หรือ WinForms เธรดจะตรวจสอบให้แน่ใจว่า chunks ของเมธอด async เกิดขึ้นเนื่องจากเป็นส่วนหนึ่งของการประมวลผลของคิวข้อความ
  2. หากด้ายเข้ามา awaitอยู่บนเธรดพูลเธรดดังนั้นชิ้นส่วนที่เหลือของเมธอด async จะเกิดขึ้นที่ใดก็ได้บนเธรดพูล

นั่นเป็นสาเหตุที่คุณอาจพบปัญหา - การใช้วิธี async พยายามเรียกใช้ส่วนที่เหลือใน Dispatcher - แม้ว่าจะถูกระงับ

.... สำรอง! ....

ฉันต้องถามคำถามทำไมคุณพยายามบล็อกแบบซิงโครนัสด้วยวิธีการแบบซิงโครนัส? การทำเช่นนั้นจะเอาชนะวัตถุประสงค์ที่ว่าทำไมวิธีที่ต้องการให้เรียกแบบอะซิงโครนัส โดยทั่วไปเมื่อคุณเริ่มใช้งานawaitในวิธีเลือกจ่ายงานหรือ UI คุณจะต้องเปลี่ยน async โฟลว์ UI ทั้งหมดของคุณ ตัวอย่างเช่นหาก callstack ของคุณเป็นดังนี้:

  1. [Top] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFหรือWinFormsรหัส
  6. [วงวนข้อความ] - WPFหรือWinFormsวงวนข้อความ

จากนั้นเมื่อมีการแปลงรหัสให้ใช้ async โดยทั่วไปคุณจะต้องจบด้วย

  1. [Top] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFหรือWinFormsรหัส
  6. [วงวนข้อความ] - WPFหรือWinFormsวงวนข้อความ

ตอบรับจริง

คลาส AsyncHelpers ด้านบนใช้งานได้จริงเพราะมันทำงานเหมือนวนข้อความที่ซ้อนกัน แต่มันจะติดตั้งกลไกแบบขนานของตัวเองไปยัง Dispatcher แทนที่จะพยายามดำเนินการบน Dispatcher เอง นั่นเป็นวิธีแก้ปัญหาหนึ่งสำหรับปัญหาของคุณ

วิธีแก้ปัญหาอื่นคือการเรียกใช้เมธอด async ของคุณบนเธรดพูลเธรดแล้วรอจนเสร็จสมบูรณ์ การทำเช่นนี้เป็นเรื่องง่าย - คุณสามารถทำได้ด้วยตัวอย่างต่อไปนี้:

var customerList = TaskEx.RunEx(GetCustomers).Result;

API ขั้นสุดท้ายจะเป็น Task.Run (... ) แต่ด้วย CTP คุณจะต้องใช้คำต่อท้าย Ex ( คำอธิบายที่นี่ )


+1 สำหรับคำอธิบายโดยละเอียดอย่างไรก็ตามTaskEx.RunEx(GetCustomers).Resultแอปพลิเคชันหยุดทำงานเมื่อเรียกใช้บนเธรดตัวแจกจ่ายที่ถูกระงับ นอกจากนี้เมธอด GetCustomers () มักเรียกใช้ async อย่างไรก็ตามในสถานการณ์หนึ่งที่จำเป็นต้องเรียกใช้แบบซิงโครนัสดังนั้นฉันจึงค้นหาวิธีการดังกล่าวโดยไม่ต้องสร้างวิธีการซิงค์เวอร์ชัน
ราเชล

+1 สำหรับ "ทำไมคุณพยายามบล็อกแบบซิงโครนัสด้วยวิธีการ" มีวิธีใช้asyncวิธีการที่เหมาะสมอยู่เสมอ ลูปซ้อนกันควรหลีกเลี่ยงอย่างแน่นอน
Stephen Cleary

24

มันทำงานได้ดีสำหรับฉัน

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

คุณต้องใช้เมธอดTask.Unwrapเพราะคำสั่งTask.Waitของคุณทำให้เกิดการรองานด้านนอก (สร้างโดยTask.Run ) ไม่ใช่สำหรับการรอคอยภายในtงานที่ส่งผ่านเป็นพารามิเตอร์ของเมธอดส่วนขยาย วิธีการของคุณTask.Runส่งคืนไม่ใช่งาน <T> แต่เป็นงาน < Tat <T>> ในสถานการณ์บางอย่างง่ายวิธีการแก้ปัญหาของคุณอาจเป็นเพราะผลงานของการเพิ่มประสิทธิภาพ TaskScheduler เช่นใช้TryExecuteTaskInlineวิธีการในการดำเนินงานภายในเธรดปัจจุบันในระหว่างรอการดำเนินกรุณาดูที่ความคิดเห็นของฉันไปนี้คำตอบ
sgnsajgon

1
นั่นไม่ถูกต้อง Task.Run จะส่งคืนงาน <T> ดูเกินmsdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement

สิ่งนี้ควรใช้อย่างไร? การหยุดชะงักนี้ใน WPF:MyAsyncMethod().RunTaskSynchronously();
ygoe

18

วิธีที่ง่ายที่สุดที่ฉันพบว่ารันภารกิจแบบซิงโครนัสและไม่มีการบล็อกเธรด UI คือการใช้ RunSynchronously () เช่น:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

ในกรณีของฉันฉันมีเหตุการณ์ที่เกิดขึ้นเมื่อมีบางอย่างเกิดขึ้น ฉันไม่รู้ว่ามันจะเกิดขึ้นกี่ครั้ง ดังนั้นฉันใช้รหัสข้างต้นในเหตุการณ์ของฉันดังนั้นเมื่อใดก็ตามที่มันเกิดเพลิงไหม้มันจะสร้างงานขึ้นมา มีการเรียกใช้งานพร้อมกันและใช้งานได้ดีสำหรับฉัน ฉันรู้สึกประหลาดใจมากที่ต้องใช้เวลานานกว่าจะค้นพบสิ่งนี้โดยพิจารณาว่ามันเรียบง่ายเพียงใด โดยปกติคำแนะนำนั้นซับซ้อนและมีข้อผิดพลาดมากกว่า นี่มันเรียบง่ายและสะอาด


1
แต่เราจะใช้วิธีนี้เมื่อรหัส async คืนสิ่งที่เราต้องการได้อย่างไร
S.Serpooshan

16

ฉันต้องเผชิญกับมันสองสามครั้งส่วนใหญ่ในการทดสอบหน่วยหรือในการพัฒนาบริการ windows ปัจจุบันฉันใช้คุณสมบัตินี้อยู่เสมอ:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

มันง่ายง่ายและฉันก็ไม่มีปัญหา


นี่เป็นสิ่งเดียวที่ไม่ได้หยุดชะงักสำหรับฉัน
AndreFeijo

15

ฉันพบรหัสนี้ที่ส่วนประกอบ Microsoft.AspNet.Identity.Core และใช้งานได้

private static readonly TaskFactory _myTaskFactory = new 
     TaskFactory(CancellationToken.None, TaskCreationOptions.None, 
     TaskContinuationOptions.None, TaskScheduler.Default);

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}


13

ทราบเพียงเล็กน้อย - วิธีการนี้:

Task<Customer> task = GetCustomers();
task.Wait()

ทำงานให้กับ WinRT

ให้ฉันอธิบาย:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

นอกจากนี้วิธีการนี้ใช้ได้กับโซลูชัน Windows Store เท่านั้น!

หมายเหตุ:วิธีนี้จะไม่ปลอดภัยสำหรับเธรดถ้าคุณเรียกวิธีการของคุณภายในวิธีการ async อื่น ๆ (ตามความคิดเห็นของ @Servy)


ฉันอธิบายวิธีแก้ปัญหานี้ให้ตรวจสอบส่วนการแก้ไข
RredCat

2
สิ่งนี้สามารถทำให้เกิดการหยุดชะงักได้ง่ายมากเมื่อถูกเรียกในสถานการณ์แบบอะซิงโครนัส
Servy

@Servy ทำให้รู้สึก ดังนั้นเมื่อฉันแก้ไขให้ถูกต้องโดยใช้ Wait (timeOut) สามารถช่วยได้ใช่ไหม
RredCat

1
จากนั้นคุณต้องกังวลเกี่ยวกับการหมดเวลาที่จะถึงเมื่อการดำเนินการไม่ได้ทำจริงซึ่งไม่ดีมากและเวลาที่ใช้รอจนกระทั่งหมดเวลาในกรณีที่มันหยุดชะงัก (และในกรณีที่คุณยังคงดำเนินต่อไป ในเมื่อยังไม่เสร็จ) ไม่เลยนั่นไม่ได้ช่วยแก้ปัญหา
Servy

@Servy ดูเหมือนว่าฉันจะต้องใช้CancellationTokenสำหรับการแก้ปัญหาของฉัน
RredCat

10

ในรหัสของคุณรองานแรกที่จะดำเนินการ แต่คุณยังไม่ได้เริ่มมันจึงรออย่างไม่มีกำหนด ลองสิ่งนี้:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

แก้ไข:

คุณบอกว่าคุณได้รับการยกเว้น โปรดโพสต์รายละเอียดเพิ่มเติมรวมถึงการติดตามสแต็ก
Mono มีกรณีทดสอบดังต่อไปนี้:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

ตรวจสอบว่ามันเหมาะกับคุณหรือไม่ หากไม่เป็นเช่นนั้นไม่น่าเป็นไปได้มากที่คุณอาจมีโครงสร้างแปลก ๆ ของ Async CTP หากใช้งานได้คุณอาจต้องการตรวจสอบสิ่งที่คอมไพเลอร์สร้างขึ้นและการTaskสร้างอินสแตนซ์แตกต่างจากตัวอย่างนี้อย่างไร

แก้ไข # 2:

ฉันจะตรวจสอบกับสะท้อนว่าข้อยกเว้นที่คุณอธิบายเกิดขึ้นเมื่อเป็นm_action nullนี่เป็นเรื่องแปลก แต่ฉันไม่เชี่ยวชาญใน Async CTP ที่ผมกล่าวว่าคุณควรแยกรหัสของคุณและดูวิธีการว่าTaskจะถูก instantiated ใดวิธีมาของมันคือm_actionnull


PS การจัดการกับ downvotes เป็นครั้งคราวคืออะไร? สนใจที่จะทำอย่างละเอียด?


ฉันปรับคำถามของฉันเพื่อให้รหัสที่ฉันพยายามชัดเจนยิ่งขึ้น RunSynchronously RunSynchronously may not be called on a task unbound to a delegateส่งกลับข้อผิดพลาดของ Google ไม่ได้รับความช่วยเหลือเนื่องจากผลลัพธ์ทั้งหมดเป็นภาษาจีน ...
Rachel

ฉันคิดว่าความแตกต่างคือฉันไม่ได้สร้างงานแล้วลองเรียกใช้งาน งานจะถูกสร้างขึ้นโดยวิธี async แทนเมื่อใช้awaitคำหลัก ข้อยกเว้นที่โพสต์ในความคิดเห็นก่อนหน้าของฉันคือข้อยกเว้นที่ฉันได้รับแม้ว่าจะเป็นหนึ่งในไม่กี่แห่งที่ฉันไม่สามารถใช้ Google และค้นหาสาเหตุหรือวิธีแก้ปัญหา
Rachel

1
asyncและasyncคำหลักไม่มีอะไรมากไปกว่าไวยากรณ์น้ำตาล คอมไพเลอร์สร้างรหัสที่จะสร้างTask<Customer>ในGetCustomers()ดังนั้นนั่นคือสิ่งที่ฉันจะดูครั้งแรก สำหรับข้อยกเว้นคุณโพสต์ข้อความยกเว้นซึ่งไม่มีประโยชน์โดยไม่มีประเภทข้อยกเว้นและการติดตามสแต็ก ToString()วิธีการยกเว้นของการโทรและโพสต์ผลลัพธ์ในคำถาม
Dan Abramov

@gaearon: ฉันโพสต์รายละเอียดข้อยกเว้นและติดตามสแต็คในคำถามเดิมของฉัน
Rachel

2
@gaearon ฉันคิดว่าคุณมี downvotes เพราะโพสต์ของคุณไม่สามารถใช้ได้กับคำถาม การสนทนาเป็นเรื่องเกี่ยวกับวิธีการรอ async ไม่ใช่เรื่องง่าย ๆ กับวิธีการส่งคืนงาน ยิ่งไปกว่านั้นในความเห็นของฉันกลไกการรอคอย async นั้นเป็นรูปแบบของน้ำตาล แต่ไม่ค่อยน่าสนใจ - มีความต่อเนื่องการจับบริบทบริบทของท้องถิ่นกลับมาทำงานต่อการจัดการข้อยกเว้นในท้องถิ่นที่ดีขึ้นและอื่น ๆ จากนั้นคุณไม่ควรเรียกใช้วิธีRunSynchronouslyในผลลัพธ์ของวิธีการ async เนื่องจากโดยวิธีแบบอะซิงโครนัสคำจำกัดความควรส่งคืนงานที่มีการกำหนดอย่างน้อยในปัจจุบันและมากกว่าหนึ่งครั้งอยู่ในสถานะกำลังทำงาน
sgnsajgon

9

ทดสอบใน. Net 4.6 นอกจากนี้ยังสามารถหลีกเลี่ยงการหยุดชะงัก

สำหรับวิธีการ async Taskกลับ

Task DoSomeWork();
Task.Run(async () => await DoSomeWork()).Wait();

สำหรับวิธีการส่งกลับ async Task<T>

Task<T> GetSomeValue();
var result = Task.Run(() => GetSomeValue()).Result;

แก้ไข :

หากผู้เรียกใช้ในเธรดพูลเธรด (หรือผู้เรียกยังอยู่ในภารกิจด้วย) ผู้เรียกนั้นยังอาจทำให้เกิดการหยุดชะงักในบางสถานการณ์


1
คำตอบของฉันหลังจากเกือบ 8 ปีที่ผ่านมา :) ตัวอย่างที่สอง - จะสร้างการหยุดชะงักในบริบทที่กำหนดไว้ทั้งหมดที่ใช้เป็นหลัก (แอปคอนโซล /. NET core / แอปเดสก์ท็อป / ... ) ที่นี่คุณมีภาพรวมเพิ่มเติมสิ่งที่ฉันกำลังพูดถึงในขณะนี้: medium.com/rubrikkgroup/…
W92

Resultเหมาะสำหรับงานถ้าคุณต้องการการโทรแบบซิงโครนัสและเป็นอันตรายอย่างยิ่ง ไม่มีสิ่งใดในชื่อResultหรือใน Intellisense Resultที่ระบุว่าเป็นการเรียกการบล็อค ควรเปลี่ยนชื่อจริง ๆ
Zodman


4

ทำไมไม่สร้างการโทรเช่น:

Service.GetCustomers();

นั่นไม่ใช่ async


4
นั่นจะเป็นสิ่งที่ฉันทำถ้าฉันไม่สามารถทำงานได้ ... สร้างรุ่นซิงค์นอกเหนือจากรุ่น Async
Rachel

3

คำตอบนี้ถูกออกแบบมาสำหรับทุกคนที่ใช้ WPF สำหรับ. NET 4.5

หากคุณพยายามเรียกใช้งานTask.Run()บนเธรด GUI ดังนั้นtask.Wait()จะไม่มีการหยุดทำงานหากคุณไม่มีasyncคำหลักในนิยามฟังก์ชันของคุณ

วิธีการขยายนี้แก้ไขปัญหาโดยการตรวจสอบเพื่อดูว่าเราอยู่ในเธรด GUI และถ้าใช่ให้เรียกใช้งานบนเธรด WPF โปรแกรมเลือกจ่ายงาน

คลาสนี้สามารถทำหน้าที่เป็นตัวเชื่อมระหว่างโลกของ async / await และโลกที่ไม่ใช่ async / await ในสถานการณ์ที่ไม่สามารถหลีกเลี่ยงได้เช่นคุณสมบัติ MVVM หรือการพึ่งพา API อื่น ๆ ที่ไม่ได้ใช้ async / await

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}

3

เพียงแค่เรียก.Result;หรือ.Wait()มีความเสี่ยงสำหรับการหยุดชะงักตามที่หลายคนได้กล่าวไว้ในความคิดเห็น เนื่องจากพวกเราส่วนใหญ่ชอบ oneliners คุณสามารถใช้สิ่งเหล่านี้เพื่อ.Net 4.5<

การรับค่าผ่านเมธอด async:

var result = Task.Run(() => asyncGetValue()).Result;

เรียกวิธี async แบบซิงโครนัส

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

Task.Runไม่มีปัญหาการหยุดชะงักจะเกิดขึ้นเนื่องจากการใช้งานของ

ที่มา:

https://stackoverflow.com/a/32429753/3850405


1

ฉันคิดว่าวิธีการช่วยเหลือต่อไปนี้สามารถแก้ไขปัญหาได้เช่นกัน

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

สามารถใช้วิธีต่อไปนี้:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

1
โปรดอธิบายการลงคะแนน
donttellya

2
... ฉันยังสนใจอย่างมากว่าทำไมคำตอบนี้จึงถูกโหวต?
donttellya

ไม่ใช่จริง "ซิงโครนัส" คุณสร้างเธรดที่สองและรอผลลัพธ์แรกของอื่น ๆ
tmt

และทุกสิ่งด้วยกันนี่เป็นความคิดที่แย่มาก
Dan Pantry

1
ฉันเพิ่งเขียนรหัสที่เหมือนกันเกือบ (บรรทัดต่อบรรทัดเดียวกัน) แต่แทนที่จะใช้ SemaphoreSlim แทนเหตุการณ์รีเซ็ตอัตโนมัติ หวังว่าฉันจะเห็นสิ่งนี้เร็วกว่านี้ ฉันค้นหาวิธีนี้เพื่อป้องกันการหยุดชะงักและทำให้รหัส async ของคุณทำงานเหมือนที่ทำในสถานการณ์จำลองแบบอะซิงโครนัสจริง ไม่แน่ใจจริงๆว่าทำไมนี่เป็นความคิดที่ไม่ดี ดูเหมือนว่าจะสะอาดกว่าวิธีอื่น ๆ ที่ฉันเคยเห็นด้านบน
tmrog

0

มันใช้งานได้สำหรับฉัน

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static void RunSync(Func<Task> func)
        {
            _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }
    }

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}

-1

ฉันได้พบว่า SpinWait ใช้งานได้ดีสำหรับเรื่องนี้

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

วิธีการข้างต้นไม่จำเป็นต้องใช้. Result หรือ. Wait () นอกจากนี้ยังช่วยให้คุณระบุการหมดเวลาเพื่อที่คุณจะได้ไม่ติดขัดตลอดไปในกรณีที่งานไม่เสร็จสมบูรณ์


1
Downvote แนะนำว่าบางคนไม่ชอบวิธีนี้ มีใครบ้างที่สามารถแสดงความคิดเห็นในข้อเสียของเรื่องนี้?
Grax32

ในกรณีที่ไม่มีผู้ลงคะแนนเสียงพูดว่าทำไมใครได้รับ downvote ใครบ้างที่สามารถโหวตได้ :-)
เคอร์ติส

1
นี่คือการสำรวจ (การหมุน) ผู้ได้รับมอบหมายจะรับเธรดจากพูลได้มากถึง 1,000 ครั้งต่อวินาที มันอาจไม่กลับมาควบคุมทันทีหลังจากงานเสร็จสิ้น (มากถึง10 + ms error) หากหมดเวลางานจะยังคงทำงานต่อไปซึ่งจะทำให้การหมดเวลาใช้งานจริงไร้ประโยชน์
Sinatr

อันที่จริงฉันใช้มันทั่วทุกที่ในรหัสของฉันและเมื่อเป็นไปตามเงื่อนไข SpinWaitSpinUntil () ออกทันที ดังนั้นสิ่งใดก็ตามที่มาก่อน 'พบเงื่อนไข' หรือหมดเวลางานจะออก มันไม่ได้ทำงานต่อไป
เคอร์ติส

-3

ใน wp8:

ห่อมัน:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

เรียกมันว่า:

GetCustomersSynchronously();

3
ไม่นี่ใช้งานได้เพราะงานไม่รอผู้แทนจากนวกรรมิก (เป็นตัวแทนและไม่ใช่งาน .. )
Rico Suter

-4
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }

-5

หรือคุณสามารถไปกับ:

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

สำหรับการคอมไพล์ให้แน่ใจว่าคุณอ้างอิงชุดประกอบส่วนขยาย:

System.Net.Http.Formatting

-9

ลองรหัสต่อไปนี้ใช้งานได้สำหรับฉัน:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.