Làm cách nào để tôi chạy phương thức async Nhiệm vụ <T> một cách đồng bộ?


628

Tôi đang tìm hiểu về async / await, và gặp phải tình huống tôi cần gọi một phương thức async một cách đồng bộ. Làm thế nào tôi có thể làm điều đó?

Phương pháp không đồng bộ:

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

Sử dụng bình thường:

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

Tôi đã thử sử dụng như sau:

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

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

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

Tôi cũng đã thử một đề nghị từ đây , tuy nhiên nó không hoạt động khi người điều phối ở trạng thái lơ lửng.

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();
}

Đây là trường hợp ngoại lệ và ngăn xếp từ cuộc gọi RunSynchronously:

System.InvalidOperationException

Thông báo : RunSyn đồng bộ có thể không được gọi trong một nhiệm vụ không gắn kết với một đại biểu.

Nội bộ ngoại lệ : null

Nguồn : 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
Câu trả lời tốt nhất cho câu hỏi "Làm cách nào tôi có thể gọi một phương thức async một cách đồng bộ" là "không". Có những hack để cố gắng buộc nó hoạt động, nhưng tất cả chúng đều có những cạm bẫy rất tinh vi. Thay vào đó, hãy sao lưu và sửa mã khiến bạn "cần" để làm điều này.
Stephen Cleary

57
@Stephen Cleary Hoàn toàn đồng ý, nhưng đôi khi điều đó đơn giản là không thể tránh khỏi, chẳng hạn như khi mã của bạn phụ thuộc vào một số API của bên thứ 3 không sử dụng async / await. Ngoài ra, nếu liên kết với các thuộc tính WPF khi sử dụng MVVM, thì không thể sử dụng async / await theo nghĩa đen vì điều này không được hỗ trợ trên các thuộc tính.
Contango

3
@StephenCleary Không phải lúc nào. Tôi đang xây dựng một DLL sẽ được nhập vào GeneXus . Nó không hỗ trợ async / đang chờ từ khóa, vì vậy tôi chỉ phải sử dụng các phương thức đồng bộ.
Dinei

5
@StephenCleary 1) GeneXus là công cụ pt thứ 3 và tôi không có quyền truy cập vào mã nguồn của nó; 2) GeneXus thậm chí không có triển khai "chức năng", vì vậy tôi không thể nhận ra làm thế nào tôi có thể thực hiện "gọi lại" với loại điều này. Chắc chắn đó sẽ là một cách giải quyết khó khăn hơn so với việc sử dụng Taskđồng bộ; 3) Tôi đang tích hợp GeneXus với trình điều khiển MongoDB C # , trình bày này chỉ hiển thị một số phương thức không đồng bộ
Dinei

1
@ygoe: Sử dụng khóa tương thích async, chẳng hạn như SemaphoreSlim.
Stephen Cleary

Câu trả lời:


456

Đây là một cách giải quyết tôi thấy rằng nó hoạt động cho tất cả các trường hợp (bao gồm cả người điều phối bị đình chỉ). Đó không phải là mã của tôi và tôi vẫn đang làm việc để hiểu đầy đủ về nó, nhưng nó vẫn hoạt động.

Nó có thể được gọi bằng cách sử dụng:

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

Mã là từ đây

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
Đối với một số nền tảng về cách thức này hoạt động, Stephen Toub (Mr Parallel) đã viết một loạt bài viết về điều này. Phần 1 Phần 2 Phần 3
Cameron MacFarland

18
Tôi đã cập nhật mã của John để hoạt động mà không cần gói các tác vụ trong lambdas: github.com/tejacques/AsyncBridge . Về cơ bản, bạn làm việc với các khối không đồng bộ với câu lệnh sử dụng. Bất cứ điều gì trong một khối sử dụng xảy ra không đồng bộ, với sự chờ đợi ở cuối. Nhược điểm là bạn cần tự mở khóa tác vụ trong cuộc gọi lại, nhưng nó vẫn khá thanh lịch, đặc biệt nếu bạn cần gọi một số chức năng không đồng bộ cùng một lúc.
Tom Jacques

17
@StephenCleary Mặc dù tôi thường đồng ý với bạn rằng mã phải được đồng bộ hóa hoàn toàn , đôi khi bạn thấy mình trong một tình huống không thể thực hiện được khi người ta phải buộc nó như một cuộc gọi đồng bộ. Về cơ bản, tình huống của tôi là tất cả các mã truy cập dữ liệu của tôi đều theo kiểu không đồng bộ. Tôi cần xây dựng sơ đồ trang web dựa trên sơ đồ trang web và thư viện bên thứ ba tôi đang sử dụng là MvcSitemap. Bây giờ khi một người đang mở rộng nó thông qua DynamicNodeProviderBaselớp cơ sở, người ta không thể khai báo nó như là một asyncphương thức. Hoặc tôi phải thay thế bằng một thư viện mới, hoặc chỉ cần gọi một op đồng bộ.
justin.lovell

6
@ justin.lovell: Có, giới hạn thư viện có thể buộc chúng tôi đưa vào hack, ít nhất là cho đến khi thư viện được cập nhật. Có vẻ như MvcSitemap là một tình huống như vậy trong đó cần phải có hack (bộ lọc MVC và các hành động con cũng vậy); Tôi chỉ can ngăn mọi người từ điều này nói chung vì những vụ hack như thế này được sử dụng quá thường xuyên khi không cần thiết. Đặc biệt với MVC, một số API ASP.NET/MVC giả định rằng chúng có một AspNetSynchronizationContext, do đó, việc hack cụ thể này sẽ không hoạt động nếu bạn gọi các API đó.
Stephen Cleary

5
Mã này sẽ không hoạt động. Nếu nó được gọi từ một pool pool, nó có thể kích hoạt bế tắc đói-thread. Người gọi của bạn sẽ chặn chờ hoạt động hoàn tất, điều này có thể không bao giờ xảy ra nếu anh ta đã cạn kiệt nhóm luồng. Xem bài viết này .
ZunTzu

318

Được thông báo câu trả lời này là ba tuổi. Tôi đã viết nó chủ yếu dựa trên trải nghiệm với .Net 4.0 và rất ít với 4.5 đặc biệt là vớiasync-await . Nói chung đó là một giải pháp đơn giản tốt đẹp, nhưng đôi khi nó phá vỡ mọi thứ. Xin vui lòng đọc các cuộc thảo luận trong các ý kiến.

.Net 4.5

Chỉ cần sử dụng này:

// 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();

Xem: TaskAwaiter , Task.Result , Task.RunSyn đồng bộ


.Net 4.0

Dùng cái này:

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

x.AsyncWaitHandle.WaitOne();

... Hoặc cái này:

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

67
.Resultcó thể tạo ra bế tắc trong kịch bản nhất định
Jordy Langen

122
Resultcó thể dễ dàng gây ra bế tắc trong async , như tôi mô tả trên blog của mình.
Stephen Cleary

8
@StephenCleary Tôi đã đọc bài viết của bạn và tự mình thử nó. Tôi thành thật nghĩ rằng ai đó ở microsoft đã thực sự say ... Đó là vấn đề tương tự như winforms và chủ đề nền ....
AK_

9
Câu hỏi liên quan đến một Nhiệm vụ được trả về bằng phương thức async. Loại đó của công tác có thể đã được bắt đầu, thực hiện, hoặc hủy bỏ, vì vậy sử dụng Task.RunSynchronously phương pháp có thể dẫn đến InvalidOperationException . Xem trang MSDN: Task.RunSynyncly Phương thức . Bên cạnh đó, công tác mà có lẽ được tạo ra bởi Task.Factory.StartNew hoặc Task.Run phương pháp (phương pháp async bên trong), vì vậy nó là nguy hiểm để cố gắng khởi động lại nó. Một số điều kiện cuộc đua có thể xảy ra trong thời gian chạy. Trong tay othe, Task.WaitTask.Result có thể khiến tôi bế tắc.
sgnsajgon

4
Chạy đồng bộ làm việc cho tôi ... Tôi không biết nếu tôi thiếu thứ gì đó nhưng điều này có vẻ thích hợp hơn với sự khủng khiếp của câu trả lời được đánh dấu - Tôi chỉ tìm cách tắt async để kiểm tra mã chỉ dừng ở đó ui từ treo
JonnyRaa

121

Ngạc nhiên không ai đề cập đến điều này:

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

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

Không đẹp như một số phương pháp khác ở đây, nhưng nó có những lợi ích sau:

  • nó không nuốt ngoại lệ (như Wait)
  • nó sẽ không bao gồm bất kỳ trường hợp ngoại lệ nào được ném vào AggregateException(như Result)
  • làm việc cho cả hai TaskTask<T>( hãy tự mình thử! )

Ngoài ra, vì GetAwaiterđược gõ vịt, nên nó hoạt động cho bất kỳ đối tượng nào được trả về từ một phương thức không đồng bộ (như ConfiguredAwaitablehoặc YieldAwaitable), không chỉ Nhiệm vụ.


chỉnh sửa: Xin lưu ý rằng phương pháp này (hoặc sử dụng .Result) có thể bị bế tắc, trừ khi bạn đảm bảo thêm .ConfigureAwait(false)mỗi khi bạn chờ đợi, cho tất cả các phương thức không đồng bộ có thể đạt được BlahAsync()(không chỉ các phương thức mà nó gọi trực tiếp). Giải thích .

// 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.

Nếu bạn quá lười để thêm .ConfigureAwait(false)ở mọi nơi và bạn không quan tâm đến hiệu suất, bạn có thể thay thế

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

1
Làm việc cho tôi cho những thứ đơn giản. Ngoài ra, nếu phương thức trả về IAsyncOperation, trước tiên tôi phải chuyển đổi nó thành Nhiệm vụ: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Lee McPherson

3
Điều này gây ra sự bế tắc bên trong một phương pháp web asmx. Tuy nhiên, gói cuộc gọi phương thức trong một Nhiệm vụ.Run () đã khiến nó hoạt động: Nhiệm vụ.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Augusto Barreto

Tôi thích cách tiếp cận này tốt nhất về mặt cú pháp bởi vì nó không liên quan đến lambdas.
dythim

25
Vui lòng KHÔNG chỉnh sửa câu trả lời của người khác để chèn liên kết đến câu hỏi của riêng bạn. Nếu bạn tin rằng câu trả lời của bạn tốt hơn, hãy để lại bình luận.
Rachel

1
docs.microsoft.com/en-us/dotnet/api/ , nói về GetAwaiter()"Phương pháp này dành cho người dùng trình biên dịch thay vì sử dụng trực tiếp trong mã."
Theophilus

75

Việc chạy tác vụ trên nhóm luồng đơn giản hơn nhiều so với việc cố gắng lừa bộ lập lịch để chạy nó một cách đồng bộ. Bằng cách đó bạn có thể chắc chắn rằng nó sẽ không bế tắc. Hiệu suất bị ảnh hưởng vì chuyển đổi ngữ cảnh.

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
Sau đó, bạn gọi task.Wait (). Kiểu dữ liệu đơn giản là Nhiệm vụ.
Michael L Perry

1
Chúng ta hãy giả sử rằng DoS SomethingAsync () là toàn bộ phương thức async chạy dài (bên trong nó đang chờ một tác vụ dài), nhưng nó nhanh chóng trả lại một điều khiển luồng cho người gọi của nó, do đó công việc đối số lambda cũng kết thúc nhanh chóng. Kết quả của Tusk.Run () có thể Nhiệm vụ <Nhiệm vụ> hoặc Nhiệm vụ <Nhiệm vụ <>> , vì vậy bạn đang chờ kết quả của nhiệm vụ bên ngoài được hoàn thành nhanh chóng, nhưng nhiệm vụ bên trong (do chờ công việc chạy dài trong phương thức không đồng bộ) vẫn đang chạy Kết luận là có lẽ chúng ta cần sử dụng phương pháp Unwrap () (như đã được thực hiện trong bài đăng @ J.Lennon) để đạt được hành vi đồng bộ của phương thức async.
sgnsajgon

5
@sgnsajgon Bạn sai rồi. Task.Run khác với Task.Factory.StartNew ở chỗ nó tự động hủy kết quả. Xem bài viết này .
ZunTzu

1
Tôi chỉ có thể viết Task.Run(DoSomethingAsync)thay thế? Điều này loại bỏ một cấp đại biểu.
ygoe

1
Vâng. Tuy nhiên, đi theo hướng ngược lại, như trong Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());rõ ràng hơn và giải quyết mối quan tâm của @sgnsajgon rằng nó có thể sẽ trả về một Nhiệm vụ <Nhiệm vụ <MyResult >>. Quá tải chính xác của Task.Run được chọn theo một trong hai cách, nhưng ủy nhiệm async làm cho ý định của bạn rõ ràng.
Michael L Perry

57

Tôi đang tìm hiểu về async / await, và gặp phải tình huống tôi cần gọi một phương thức async một cách đồng bộ. Làm thế nào tôi có thể làm điều đó?

Câu trả lời tốt nhất là bạn không , với các chi tiết phụ thuộc vào "tình huống" là gì.

Nó có phải là một getter / setter không? Trong hầu hết các trường hợp, tốt hơn là có các phương thức không đồng bộ hơn là "thuộc tính không đồng bộ". (Để biết thêm thông tin, hãy xem bài đăng trên blog của tôi về các thuộc tính không đồng bộ ).

Đây có phải là ứng dụng MVVM và bạn muốn liên kết dữ liệu không đồng bộ? Sau đó sử dụng một cái gì đó giống như của tôi NotifyTask, như được mô tả trong bài viết MSDN của tôi về ràng buộc dữ liệu không đồng bộ .

Nó có phải là một nhà xây dựng? Sau đó, bạn có thể muốn xem xét một phương pháp nhà máy không đồng bộ. (Để biết thêm thông tin, hãy xem bài đăng trên blog của tôi về các nhà xây dựng không đồng bộ ).

Hầu như luôn luôn có một câu trả lời tốt hơn là thực hiện đồng bộ hóa quá mức.

Nếu tình huống của bạn không thể xảy ra (và bạn biết điều này bằng cách đặt câu hỏi ở đây mô tả tình huống ), thì tôi khuyên bạn chỉ nên sử dụng mã đồng bộ. Async tất cả các cách là tốt nhất; đồng bộ tất cả các cách là tốt nhất thứ hai. Đồng bộ hóa không đồng bộ không được khuyến nghị.

Tuy nhiên, có một số tình huống cần đồng bộ hóa quá mức không đồng bộ. Cụ thể, bạn bị ràng buộc bởi mã gọi để bạn phải đồng bộ hóa (và hoàn toàn không có cách nào để suy nghĩ lại hoặc cấu trúc lại mã của bạn để cho phép không đồng bộ) bạn phải gọi mã async. Đây là một tình huống rất hiếm, nhưng đôi khi nó xuất hiện.

Trong trường hợp đó, bạn sẽ cần sử dụng một trong những hack được mô tả trong bài viết của tôi về phát triển trường nâuasync , cụ thể:

  • Chặn (ví dụ GetAwaiter().GetResult():). Lưu ý rằng điều này có thể gây ra bế tắc (như tôi mô tả trên blog của tôi).
  • Chạy mã trên một chuỗi chủ đề nhóm (ví dụ, Task.Run(..).GetAwaiter().GetResult()). Lưu ý rằng điều này sẽ chỉ hoạt động nếu mã không đồng bộ có thể chạy trên luồng nhóm luồng (nghĩa là không phụ thuộc vào bối cảnh UI hoặc ASP.NET).
  • Các vòng lặp tin nhắn lồng nhau. Lưu ý rằng điều này sẽ chỉ hoạt động nếu mã không đồng bộ chỉ giả sử một bối cảnh đơn luồng, không phải là một loại ngữ cảnh cụ thể (rất nhiều mã UI và ASP.NET mong đợi một bối cảnh cụ thể).

Các vòng lặp tin nhắn lồng nhau là nguy hiểm nhất trong tất cả các hack, bởi vì nó gây ra lối vào lại . Lý do nhập lại cực kỳ khó khăn để lý do và (IMO) là nguyên nhân của hầu hết các lỗi ứng dụng trên Windows. Cụ thể, nếu bạn đang sử dụng luồng UI và bạn chặn hàng đợi công việc (chờ công việc async hoàn thành), thì CLR thực sự thực hiện một số thông báo cho bạn - nó thực sự sẽ xử lý một số tin nhắn Win32 từ bên trong bạn mã . Ồ, và bạn không biết tin nhắn nào - khi Chris Brumme nói "Sẽ không tuyệt vời khi biết chính xác những gì sẽ được bơm? Thật không may, bơm là một nghệ thuật đen vượt quá tầm hiểu biết." , sau đó chúng tôi thực sự không có hy vọng để biết.

Vì vậy, khi bạn chặn như thế này trên một luồng UI, bạn sẽ gặp rắc rối. Một trích dẫn khác từ cùng một bài báo: "Thỉnh thoảng, khách hàng trong hoặc ngoài công ty phát hiện ra rằng chúng tôi đang bơm tin nhắn trong khi chặn được quản lý trên STA [luồng UI]. Đây là một mối quan tâm chính đáng, bởi vì họ biết rằng nó rất khó để viết mã mạnh mẽ khi đối mặt với sự hồi sinh. "

Vâng, đúng vậy. Rất khó để viết mã đó là mạnh mẽ khi đối mặt với reentrancy. Và các vòng lặp thông điệp lồng nhau buộc bạn phải viết mã mạnh mẽ khi đối mặt với reentrancy. Đây là lý do tại sao câu trả lời được chấp nhận (và được đánh giá cao nhất) cho câu hỏi này là cực kỳ nguy hiểm trong thực tế.

Nếu bạn hoàn toàn không có tất cả các tùy chọn khác - bạn không thể thiết kế lại mã của mình, bạn không thể cấu trúc lại nó thành không đồng bộ - bạn bị ép buộc bởi mã gọi không thể thay đổi được đồng bộ hóa - bạn không thể thay đổi mã xuôi dòng để được đồng bộ hóa - bạn không thể chặn - bạn không thể chạy mã async trên một luồng riêng biệt - sau đó và chỉ sau đó bạn mới nên xem xét việc chấp nhận reentrancy.

Nếu bạn thấy mình ở góc này, tôi khuyên bạn nên sử dụng một cái gì đó như Dispatcher.PushFramecho các ứng dụng WPF , lặp lại với Application.DoEventscác ứng dụng WinForm và cho trường hợp chung, của riêng tôi AsyncContext.Run.


Stephen, có một rất giống qestion mà bạn cung cấp câu trả lời tuyệt vời quá. Bạn có nghĩ rằng một trong số chúng có thể được đóng lại dưới dạng trùng lặp hoặc có thể hợp nhất yêu cầu hoặc đưa lên meta trước (vì mỗi q có ~ 200 nghìn lượt xem hơn 200 phiếu) không? Gợi ý?
Alexei Levenkov

1
@AlexeiLevenkov: Tôi không cảm thấy đúng khi làm điều đó, vì một vài lý do: 1) Câu trả lời cho câu hỏi được liên kết là khá lỗi thời. 2) Tôi đã viết toàn bộ bài viết về chủ đề mà tôi cảm thấy đầy đủ hơn bất kỳ Q / A SO hiện có nào. 3) Câu trả lời được chấp nhận cho câu hỏi này là cực kỳ phổ biến. 4) Tôi phản đối kịch liệt câu trả lời được chấp nhận đó. Vì vậy, đóng cửa điều này như một bản sao đó sẽ là một sự lạm quyền; kết thúc rằng như là một bản sao của điều này (hoặc sáp nhập) sẽ trao quyền cho một câu trả lời nguy hiểm hơn nữa. Tôi để nó như vậy, và để nó cho cộng đồng.
Stephen Cleary

Đồng ý. Tôi sẽ xem xét đưa nó lên meta hơn bằng một cách nào đó.
Alexei Levenkov

9
Câu trả lời này đi một chặng đường dài trên đầu tôi. "Sử dụng async tất cả các cách xuống" là lời khuyên khó hiểu, do rõ ràng là không thể làm theo. Một chương trình với Main()phương thức async không biên dịch; tại một số điểm bạn đã để thu hẹp khoảng cách giữa đồng bộ và async thế giới. Đây không phải là một " tình huống rất hiếm" , nó thực sự cần thiết trong mọi chương trình gọi một phương thức không đồng bộ. Không có tùy chọn nào để không "thực hiện đồng bộ hóa quá mức" , chỉ là một tùy chọn để giảm bớt gánh nặng cho phương thức gọi thay vì thực hiện theo cách bạn đang viết.
Đánh dấu Amery

1
Tuyệt quá. Tôi sắp đặt asynctrên tất cả các phương pháp trong ứng dụng của tôi bây giờ. Và đó là rất nhiều. Đây không phải là mặc định?
ygoe

25

Nếu tôi đang đọc đúng câu hỏi của bạn - mã muốn cuộc gọi đồng bộ đến một phương thức không đồng bộ đang thực thi trên một chuỗi điều phối bị treo. Và bạn muốn thực sự đồng bộ chặn luồng đó cho đến khi phương thức async được hoàn thành.

Các phương thức Async trong C # 5 được hỗ trợ bằng cách cắt hiệu quả phương thức thành các phần dưới mui xe và trả về một phương Taskthức có thể theo dõi sự hoàn thành tổng thể của toàn bộ shabang. Tuy nhiên, làm thế nào các phương thức băm nhỏ thực thi có thể phụ thuộc vào loại biểu thức được truyền cho awaittoán tử.

Hầu hết thời gian, bạn sẽ sử dụng awaitbiểu thức loại Task. Việc triển khai awaitmô hình của nhiệm vụ là "thông minh" ở chỗ nó làm theo SynchronizationContext, điều này về cơ bản gây ra những điều sau đây:

  1. Nếu luồng nhập vào awaitnằm trên luồng vòng lặp thông báo của Dispatcher hoặc WinForms, thì nó đảm bảo rằng các khối của phương thức async xảy ra như là một phần của quá trình xử lý hàng đợi tin nhắn.
  2. Nếu luồng nhập vào awaitlà trên luồng của luồng xử lý, thì các đoạn còn lại của phương thức async xảy ra ở bất kỳ đâu trên nhóm luồng.

Đó là lý do tại sao bạn có thể gặp vấn đề - việc triển khai phương thức async đang cố gắng chạy phần còn lại trên Bộ điều phối - mặc dù nó bị treo.

.... sao lưu! ....

Tôi phải đặt câu hỏi, tại sao bạn lại cố gắng chặn đồng bộ trên một phương thức không đồng bộ? Làm như vậy sẽ đánh bại mục đích tại sao phương thức muốn được gọi là không đồng bộ. Nói chung, khi bạn bắt đầu sử dụng awaittrên phương thức Bộ điều phối hoặc Giao diện người dùng, bạn sẽ muốn biến toàn bộ luồng UI của mình không đồng bộ. Ví dụ: nếu callstack của bạn giống như sau:

  1. [Hàng đầu] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFhoặc WinForms
  6. [Vòng lặp tin nhắn] - WPFhoặc WinFormsVòng lặp tin nhắn

Sau đó, khi mã đã được chuyển đổi để sử dụng async, thông thường bạn sẽ kết thúc bằng

  1. [Hàng đầu] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFhoặc WinForms
  6. [Vòng lặp tin nhắn] - WPFhoặc WinFormsVòng lặp tin nhắn

Trả lời thực tế

Lớp AsyncHelpers ở trên thực sự hoạt động vì nó hoạt động giống như một vòng lặp thông báo lồng nhau, nhưng nó cài đặt cơ chế song song của riêng nó cho Bộ điều phối thay vì cố gắng thực thi trên chính Bộ điều phối. Đó là một cách giải quyết cho vấn đề của bạn.

Một cách giải quyết khác là thực thi phương thức async của bạn trên một luồng luồng, và sau đó đợi cho nó hoàn thành. Làm như vậy rất dễ dàng - bạn có thể làm điều đó với đoạn mã sau:

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

API cuối cùng sẽ là Task.Run (...), nhưng với CTP, bạn sẽ cần các hậu tố Ex ( giải thích ở đây ).


+1 cho phần giải thích chi tiết, tuy nhiên sẽ TaskEx.RunEx(GetCustomers).Resulttreo ứng dụng khi nó chạy trên luồng xử lý bị treo. Ngoài ra, phương thức GetCustomers () thường chạy không đồng bộ, tuy nhiên trong một tình huống, nó cần chạy đồng bộ, vì vậy tôi đã tìm cách để làm điều đó mà không cần xây dựng phiên bản đồng bộ của phương thức.
Rachel

+1 cho "tại sao bạn cố gắng chặn đồng bộ trên phương thức không đồng bộ?" Luôn luôn có một cách để sử dụng đúng asyncphương pháp; các vòng lặp lồng nhau chắc chắn nên tránh.
Stephen Cleary

24

Điều này đang làm việc tốt cho tôi

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;
    }
}

Bạn cũng cần sử dụng phương thức Task.Unwrap , bởi vì câu lệnh Task.Wait của bạn gây ra việc chờ đợi tác vụ bên ngoài (được tạo bởi Task.Run ), không phải cho bên trong chờ đợi Nhiệm vụ được truyền dưới dạng tham số của phương thức mở rộng. Phương thức Task.Run của bạn trả về không phải là Nhiệm vụ <T>, mà là Tác vụ <Nhiệm vụ <T >>. Trong một số trường hợp đơn giản, giải pháp của bạn có thể hoạt động do tối ưu hóa TaskScheduler, ví dụ: sử dụng phương thức TryExecuteTaskInline để thực thi Nhiệm vụ trong luồng hiện tại trong hoạt động Chờ. Vui lòng xem nhận xét của tôi cho câu trả lời này .
sgnsajgon

1
Đó là không đúng. Tác vụ.Run sẽ trả về Tác vụ <T>. Xem quá tải này msdn.microsoft.com/en-us/l Library / hh194918 (v = vs.110) .aspx
Clement

Làm thế nào là nó được sử dụng? Sự bế tắc này trong WPF:MyAsyncMethod().RunTaskSynchronously();
ygoe

18

Cách đơn giản nhất mà tôi đã tìm thấy để chạy tác vụ một cách đồng bộ và không chặn luồng UI là sử dụng RunSynyncly () như:

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

Trong trường hợp của tôi, tôi có một sự kiện phát sinh khi có điều gì đó xảy ra. Tôi không biết nó sẽ xảy ra bao nhiêu lần. Vì vậy, tôi sử dụng mã ở trên trong sự kiện của mình, vì vậy bất cứ khi nào nó kích hoạt, nó sẽ tạo ra một nhiệm vụ. Nhiệm vụ được thực hiện đồng bộ và nó hoạt động rất tốt cho tôi. Tôi chỉ ngạc nhiên khi tôi mất quá nhiều thời gian để tìm hiểu điều này vì nó đơn giản như thế nào. Thông thường, các khuyến nghị phức tạp hơn nhiều và dễ bị lỗi. Đây là nó đơn giản và sạch sẽ.


1
Nhưng làm thế nào chúng ta có thể sử dụng phương pháp này khi mã async trả về thứ chúng ta cần?
S.Serpoo Sơn

16

Tôi đã phải đối mặt với nó một vài lần, chủ yếu là trong thử nghiệm đơn vị hoặc trong phát triển dịch vụ windows. Hiện tại tôi luôn sử dụng tính năng này:

        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");

Nó đơn giản, dễ dàng và tôi không có vấn đề gì.


Đây là người duy nhất không bế tắc đối với tôi.
AndreFeijo

15

Tôi đã tìm thấy mã này tại thành phần Microsoft.AspNet.Identity.Core và nó hoạt động.

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

Chỉ cần một lưu ý nhỏ - cách tiếp cận này:

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

hoạt động cho WinRT.

Hãy để tôi giải thích:

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"
    });
}

Hơn nữa, phương pháp này chỉ hoạt động cho các giải pháp Windows Store!

Lưu ý: Cách này không an toàn cho chủ đề nếu bạn gọi phương thức của mình bên trong phương thức async khác (theo nhận xét của @Servy)


Tôi đã giải thích giải pháp này, kiểm tra phần EDIT.
RredCat

2
Điều này rất dễ dẫn đến bế tắc khi được gọi trong các tình huống không đồng bộ.
Phục vụ

@Servy có ý nghĩa. Vì vậy, khi tôi nhận được chính xác bằng cách sử dụng Wait (timeOut) có thể giúp đỡ, phải không?
RredCat

1
Sau đó, bạn cần lo lắng về việc đạt được thời gian chờ khi hoạt động không thực sự được thực hiện, điều này rất tệ và cũng là thời gian chờ đợi cho đến khi hết thời gian trong trường hợp nó bị khóa (và trong trường hợp đó bạn vẫn tiếp tục vào khi nó không được thực hiện). Vì vậy, không, điều đó không khắc phục vấn đề.
Phục vụ

@Servy Hình như tôi phải thực hiện CancellationTokencho giải pháp của mình.
RredCat

10

Trong mã của bạn, lần đầu tiên bạn chờ tác vụ thực thi nhưng bạn chưa khởi động nó nên nó chờ vô thời hạn. Thử cái này:

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

Biên tập:

Bạn nói rằng bạn nhận được một ngoại lệ. Xin vui lòng gửi thêm chi tiết, bao gồm cả theo dõi ngăn xếp.
Mono chứa trường hợp kiểm tra sau:

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

        Assert.AreEqual (1, val);
}

Kiểm tra nếu điều này làm việc cho bạn. Nếu không, mặc dù rất khó xảy ra, bạn có thể có một số bản dựng Async CTP kỳ quặc. Nếu nó hoạt động, bạn có thể muốn kiểm tra chính xác trình biên dịch tạo ra gì và cách Taskkhởi tạo khác với mẫu này.

Chỉnh sửa # 2:

Tôi đã kiểm tra với Reflector rằng ngoại lệ bạn mô tả xảy ra khi m_actionnull. Điều này hơi lạ, nhưng tôi không phải là chuyên gia về Async CTP. Như tôi đã nói, bạn nên dịch ngược mã của bạn và xem như thế nào chính xác Taskđang được instantiated bất kỳ cách đi của nó m_actionnull.


PS Điều gì đối phó với các downvote thường xuyên? Quan tâm đến công phu?


Tôi đã điều chỉnh câu hỏi của mình để làm cho mã tôi đã cố gắng rõ ràng hơn một chút. RunSynync trả về lỗi của RunSynchronously may not be called on a task unbound to a delegate. Google không giúp được gì vì tất cả các kết quả cho điều đó đều bằng tiếng Trung Quốc ...
Rachel

Tôi nghĩ rằng sự khác biệt là tôi không tạo Nhiệm vụ và sau đó thử chạy nó. Thay vào đó, tác vụ được tạo bằng phương thức async khi awaittừ khóa được sử dụng. Ngoại lệ được đăng trong nhận xét trước đây của tôi là ngoại lệ tôi nhận được, mặc dù đó là một trong số ít trường hợp tôi không thể Google và tìm nguyên nhân hoặc giải quyết.
Rachel

1
asyncasynctừ khóa không có gì nhiều hơn cú pháp đường. Trình biên dịch tạo mã để tạo Task<Customer>trong GetCustomers()đó là nơi tôi sẽ tìm đầu tiên. Đối với ngoại lệ, bạn chỉ đăng thông báo ngoại lệ, vô dụng mà không có loại ngoại lệ và dấu vết ngăn xếp. Gọi ToString()phương thức ngoại lệ và đầu ra bài trong câu hỏi.
Dan Abramov

@gaearon: Tôi đã đăng các chi tiết ngoại lệ và theo dõi ngăn xếp trong câu hỏi ban đầu của tôi.
Rachel

2
@gaearon Tôi nghĩ bạn đã bị downvote vì bài viết của bạn không áp dụng được cho câu hỏi. Cuộc thảo luận là về các phương thức chờ đợi không đồng bộ, không phải về các phương thức trả về tác vụ đơn giản. Hơn nữa, theo tôi, cơ chế chờ đợi không đồng bộ là một cú pháp đường, nhưng không quá tầm thường - có sự tiếp tục, nắm bắt bối cảnh, tiếp tục bối cảnh địa phương, xử lý ngoại lệ cục bộ nâng cao, v.v. Sau đó, bạn không nên gọi phương thức RunSynyncly do kết quả của phương thức async, bởi vì theo định nghĩa phương thức không đồng bộ sẽ trả về Nhiệm vụ hiện đang được lên lịch ít nhất và hơn một lần ở trạng thái đang chạy.
sgnsajgon

9

Đã thử nghiệm trong .Net 4.6. Nó cũng có thể tránh bế tắc.

Đối với phương thức async trở lại Task.

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

Đối với phương thức async trở lại Task<T>

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

Chỉnh sửa :

Nếu người gọi đang chạy trong luồng nhóm luồng (hoặc người gọi cũng đang trong một tác vụ), nó vẫn có thể gây ra bế tắc trong một số trường hợp.


1
Câu trả lời của tôi sau gần 8 năm :) Ví dụ thứ hai - sẽ tạo ra sự bế tắc trong tất cả các bối cảnh được lên lịch chủ yếu được sử dụng (ứng dụng bảng điều khiển / .NET core / ứng dụng máy tính để bàn / ...). Ở đây bạn có cái nhìn tổng quan hơn về những gì tôi đang nói bây giờ: Medium.com/rubrikkgroup/ Khăn
W92

Resultlà hoàn hảo cho công việc nếu bạn muốn một cuộc gọi đồng bộ, và hết sức nguy hiểm. Không có gì trong tên Resulthoặc trong intellisense Resultcho biết đó là một cuộc gọi chặn. Nó thực sự nên được đổi tên.
Zodman

5

sử dụng đoạn mã dưới đây

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));

4

Tại sao không tạo một cuộc gọi như:

Service.GetCustomers();

đó không phải là không đồng bộ.


4
Đó sẽ là những gì tôi làm nếu tôi không thể làm việc này ... tạo phiên bản Đồng bộ hóa ngoài phiên bản Async
Rachel

3

Câu trả lời này được thiết kế cho bất kỳ ai đang sử dụng WPF cho .NET 4.5.

Nếu bạn cố thực hiện Task.Run()trên luồng GUI, thì task.Wait()sẽ bị treo vô thời hạn, nếu bạn không có asynctừ khóa trong định nghĩa hàm.

Phương thức mở rộng này giải quyết vấn đề bằng cách kiểm tra xem liệu chúng ta có đang ở trên luồng GUI hay không và nếu có, hãy chạy tác vụ trên luồng xử lý WPF.

Lớp này có thể đóng vai trò là chất kết dính giữa thế giới async / await và thế giới không async / await, trong các tình huống không thể tránh khỏi, chẳng hạn như các thuộc tính MVVM hoặc phụ thuộc vào các API khác không sử dụng 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

Đơn giản chỉ cần gọi .Result;hoặc .Wait()là một nguy cơ cho bế tắc như nhiều người đã nói trong các ý kiến. Vì hầu hết chúng ta thích oneliners, bạn có thể sử dụng chúng cho.Net 4.5<

Có được giá trị thông qua phương thức async:

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

Gọi đồng bộ một phương thức async

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

Không có vấn đề bế tắc sẽ xảy ra do việc sử dụng Task.Run.

Nguồn:

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


1

Tôi nghĩ rằng phương pháp trợ giúp sau đây cũng có thể giải quyết vấn đề.

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;
    }

Có thể được sử dụng theo cách sau:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

1
Vui lòng giải thích việc bỏ phiếu
donttellya

2
... Tôi vẫn háo hức quan tâm tại sao câu trả lời này đã bị bỏ phiếu?
donttellya

Nó không phải là "đồng bộ" thực sự .Y ou tạo hai luồng và chờ kết quả đầu tiên khác.
tmt

và tất cả mọi thứ sang một bên, đây là một ý tưởng rất xấu.
Dan Pantry

1
Tôi chỉ viết gần như mã giống nhau (từng dòng giống nhau) nhưng thay vào đó sử dụng SemaphoreSlim thay vì sự kiện tự động đặt lại. Ước gì tôi đã nhìn thấy điều này sớm hơn. Tôi tìm thấy cách tiếp cận này để ngăn chặn các bế tắc và giữ cho mã async của bạn chạy giống như trong các tình huống không đồng bộ thực sự. Không thực sự chắc chắn tại sao đây là một ý tưởng tồi. Có vẻ sạch sẽ hơn nhiều so với các phương pháp khác tôi đã thấy ở trên.
tmrog

0

Đây là công việc cho tôi

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

Tôi đã thấy rằng SpinWait hoạt động khá tốt cho việc này.

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;

Cách tiếp cận trên không cần sử dụng .Result hoặc .Wait (). Nó cũng cho phép bạn chỉ định thời gian chờ để bạn không bị kẹt mãi mãi trong trường hợp nhiệm vụ không bao giờ hoàn thành.


1
Downvote cho thấy ai đó không thích phương pháp này. Có ai đó có thể bình luận về nhược điểm của điều này?
Grax32

Trong trường hợp không có downvoter nói TẠI SAO downvote được đưa ra, ai đó có thể upvote không? :-)
Curtis

1
Đây là bỏ phiếu (quay), đại biểu sẽ lấy luồng từ nhóm lên tới 1000 lần mỗi giây. Nó có thể không trả lại quyền điều khiển ngay sau khi tác vụ kết thúc (lỗi tối đa 10 + ms ). Nếu kết thúc khi hết thời gian, tác vụ sẽ tiếp tục chạy, điều này khiến cho thời gian chờ thực tế trở nên vô dụng.
Sinatr

Trên thực tế, tôi đang sử dụng tất cả các vị trí trong mã của mình và khi điều kiện được đáp ứng, SpinWaitSpinUntil () ngay lập tức thoát ra. Vì vậy, bất cứ khi nào đến trước, 'điều kiện đáp ứng' hoặc hết thời gian, nhiệm vụ sẽ thoát. Nó không tiếp tục chạy.
Curtis

-3

Trên wp8:

Gói nó:

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

Gọi nó đi:

GetCustomersSynchronously();

3
Không, điều này sẽ không hoạt động, bởi vì nhiệm vụ không chờ đợi đại biểu từ nhà xây dựng (đó là một đại biểu và không phải là một nhiệm vụ ..)
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

Hoặc bạn có thể đi với:

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

Để biên dịch, hãy chắc chắn rằng bạn tham khảo phần mở rộng:

System.Net.Http.Formatting

-9

Hãy thử mã sau đây nó hoạt động với tôi:

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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.