Làm cách nào để gọi phương thức không đồng bộ từ phương thức đồng bộ trong C #?


863

Tôi có một public async void Foo()phương thức mà tôi muốn gọi từ phương thức đồng bộ. Cho đến nay tất cả những gì tôi thấy từ tài liệu MSDN đang gọi các phương thức async thông qua các phương thức async, nhưng toàn bộ chương trình của tôi không được xây dựng bằng các phương thức async.

Điều này thậm chí có thể?

Đây là một ví dụ về cách gọi các phương thức này từ một phương thức không đồng bộ: http://msdn.microsoft.com/en-us/l Library / hh300224 (v = vs.110) .aspx

Bây giờ tôi đang xem xét việc gọi các phương thức không đồng bộ này từ các phương thức đồng bộ hóa.


2
Tôi cũng chạy vào đây. Ghi đè Trình tạo vai trò, bạn không thể thay đổi chữ ký phương thức của phương thức GetRolesForUser để bạn không thể tạo phương thức không đồng bộ và do đó không thể sử dụng để chờ gọi api một cách đồng bộ. Giải pháp tạm thời của tôi là thêm các phương thức đồng bộ vào lớp HttpClient chung của tôi nhưng muốn biết liệu điều này có khả thi không (và ý nghĩa của nó có thể là gì).
Timothy Lee Russell

1
Bởi vì async void Foo()phương thức của bạn không trả về Tasknên nó có nghĩa là người gọi không thể biết khi nào nó hoàn thành, Taskthay vào đó nó phải trả về .
Đại

1
Liên kết một q / a liên quan về cách thực hiện điều này trên luồng UI.
chơi

Câu trả lời:


711

Lập trình không đồng bộ "phát triển" thông qua cơ sở mã. Nó đã được so sánh với một virus zombie . Giải pháp tốt nhất là cho phép nó phát triển, nhưng đôi khi điều đó là không thể.

Tôi đã viết một vài loại trong thư viện Nito.AsyncEx của mình để xử lý cơ sở mã không đồng bộ một phần. Không có giải pháp nào hoạt động trong mọi tình huống.

Giải pháp A

Nếu bạn có một phương pháp không đồng bộ đơn giản mà không cần đồng bộ hóa trở lại bối cảnh của nó, thì bạn có thể sử dụng Task.WaitAndUnwrapException:

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

Bạn không muốn sử dụng Task.Waithoặc Task.Resultvì chúng bao gồm các ngoại lệ AggregateException.

Giải pháp này chỉ phù hợp nếu MyAsyncMethodkhông đồng bộ hóa trở lại bối cảnh của nó. Nói cách khác, mỗi awaittrong MyAsyncMethodnên kết thúc bằng ConfigureAwait(false). Điều này có nghĩa là nó không thể cập nhật bất kỳ thành phần UI nào hoặc truy cập vào bối cảnh yêu cầu ASP.NET.

Giải pháp B

Nếu MyAsyncMethodkhông cần phải đồng bộ hóa trở lại bối cảnh của nó, thì bạn có thể sử dụng AsyncContext.RunTaskđể cung cấp một bối cảnh lồng nhau:

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

* Cập nhật ngày 14 tháng 4 năm 2014: Trong các phiên bản gần đây hơn của thư viện, API như sau:

var result = AsyncContext.Run(MyAsyncMethod);

(Bạn có thể sử dụng Task.Resulttrong ví dụ này vì RunTasksẽ truyền Taskngoại lệ).

Lý do bạn có thể cần AsyncContext.RunTaskthay vì Task.WaitAndUnwrapExceptionlà vì khả năng bế tắc khá tinh tế xảy ra trên WinForms / WPF / SL / ASP.NET:

  1. Phương thức đồng bộ gọi phương thức không đồng bộ, thu được a Task.
  2. Phương thức đồng bộ thực hiện chờ chặn trên Task.
  3. Các asyncphương pháp sử dụng awaitmà không cần ConfigureAwait.
  4. Không Taskthể hoàn thành trong tình huống này vì nó chỉ hoàn thành khi asyncphương thức kết thúc; các asyncphương pháp có thể không đầy đủ vì nó đang cố gắng sắp xếp tiếp nối của nó đến SynchronizationContext, và WinForms / WPF / SL / ASP.NET sẽ không cho phép việc tiếp tục chạy vì phương pháp đồng bộ vẫn đang chạy trong bối cảnh đó.

Đây là một lý do tại sao nên sử dụng ConfigureAwait(false)trong mọi asyncphương pháp càng nhiều càng tốt.

Giải pháp C

AsyncContext.RunTasksẽ không hoạt động trong mọi kịch bản. Ví dụ: nếu asyncphương thức chờ một cái gì đó yêu cầu sự kiện UI hoàn thành, thì bạn sẽ bế tắc ngay cả với bối cảnh lồng nhau. Trong trường hợp đó, bạn có thể bắt đầu asyncphương thức trên nhóm luồng:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Tuy nhiên, giải pháp này đòi hỏi một MyAsyncMethodthứ sẽ hoạt động trong bối cảnh nhóm luồng. Vì vậy, nó không thể cập nhật các thành phần UI hoặc truy cập vào bối cảnh yêu cầu ASP.NET. Và trong trường hợp đó, bạn cũng có thể thêm ConfigureAwait(false)vào các awaitcâu lệnh của nó và sử dụng giải pháp A.

Cập nhật, 2019-05-01: "Các thực tiễn tồi tệ nhất" hiện tại có trong một bài viết MSDN tại đây .


9
Giải pháp A có vẻ giống như những gì tôi muốn, nhưng có vẻ như task.WaitAndUnwrapException () đã không đưa nó vào .Net 4.5 RC; nó chỉ có nhiệm vụ.Wait (). Bất kỳ ý tưởng làm thế nào để làm điều này với phiên bản mới? Hay đây là một phương pháp mở rộng tùy chỉnh bạn đã viết?
deadlydog

3
WaitAndUnwrapExceptionlà phương pháp của riêng tôi từ thư viện AsyncEx của tôi . Các lib .NET chính thức không cung cấp nhiều trợ giúp để trộn mã đồng bộ hóa và mã không đồng bộ (và nói chung, bạn không nên làm điều đó!). Tôi đang chờ .NET 4.5 RTW và một máy tính xách tay không phải XP mới trước khi cập nhật AsyncEx để chạy trên 4.5 (Hiện tại tôi không thể phát triển cho 4.5 vì tôi bị kẹt trên XP thêm vài tuần nữa).
Stephen Cleary

12
AsyncContextbây giờ có một Runphương thức có biểu thức lambda, vì vậy bạn nên sử dụngvar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary

1
Tôi đã đưa thư viện của bạn ra khỏi Nuget, nhưng nó thực sự không có RunTaskphương pháp. Điều gần nhất tôi có thể tìm thấy là Run, nhưng điều đó không có Resulttài sản.
Asad Saeeduddin


313

Thêm một giải pháp cuối cùng đã giải quyết vấn đề của tôi, hy vọng tiết kiệm thời gian của ai đó.

Đầu tiên đọc một vài bài viết của Stephen Cleary :

Từ "hai thực tiễn tốt nhất" trong "Không chặn mã Async", cách thứ nhất không hiệu quả với tôi và cách thứ hai không áp dụng (về cơ bản nếu tôi có thể sử dụng await, tôi sẽ làm!).

Vì vậy, đây là cách giải quyết của tôi: bọc cuộc gọi bên trong a Task.Run<>(async () => await FunctionAsync());và hy vọng không còn bế tắc nữa.

Đây là mã của tôi:

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

5
Hai năm sau, tôi tò mò muốn biết giải pháp này như thế nào. Có tin gì không? Có sự tinh tế cho phương pháp này bị mất trên người mới?
Dan Esparza

26
Điều này sẽ không bế tắc, đúng, nhưng đơn giản là vì nó buộc phải chạy trong một luồng mới, bên ngoài bối cảnh đồng bộ hóa của luồng gốc. Tuy nhiên, có một số môi trường nhất định mà điều này rất khó hiểu: đặc biệt là các ứng dụng web. Điều này có thể giảm một nửa hiệu quả các luồng có sẵn cho máy chủ web (một luồng cho yêu cầu và một luồng cho việc này). Bạn càng làm điều này, nó càng tồi tệ hơn. Bạn có khả năng cuối cùng sẽ bế tắc toàn bộ máy chủ web của bạn.
Chris Pratt

30
@ChrisPratt - Bạn có thể đúng, vì đó Task.Run()không phải là cách thực hành tốt nhất trong mã async. Nhưng, một lần nữa, câu trả lời cho câu hỏi ban đầu là gì? Không bao giờ gọi một phương thức async đồng bộ? Chúng tôi muốn, nhưng trong một thế giới thực, đôi khi chúng ta phải làm.
Tohid

1
@Tohid bạn có thể thử thư viện của Stephen Cleary. Tôi đã thấy mọi người cho rằng điều này và Parallel.ForEachlạm dụng sẽ không ảnh hưởng đến "thế giới thực" và cuối cùng nó đã đánh sập các máy chủ. Mã này phù hợp với các ứng dụng Console nhưng như @ChrisPratt nói, không nên được sử dụng trong Ứng dụng web. Nó có thể hoạt động "ngay bây giờ" nhưng không thể mở rộng.
makhdumi

1
Tôi tò mò bắt đầu tạo tài khoản mới trong khi trả lời các câu hỏi chỉ để có đủ điểm để nâng cấp câu hỏi này ....
Giannis Paraskevopoulos

206

Microsoft đã xây dựng một lớp AsyncHelper (nội bộ) để chạy Async như Sync. Nguồn trông như:

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

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

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

Các lớp cơ sở Microsoft.AspNet.Identity chỉ có các phương thức Async và để gọi chúng là Sync, có các lớp với các phương thức mở rộng trông giống như (sử dụng ví dụ):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Đối với những người quan tâm về các điều khoản cấp phép của mã, đây là một liên kết đến mã rất giống nhau (chỉ cần thêm hỗ trợ cho văn hóa trên luồng) có ý kiến ​​để chỉ ra rằng đó là MIT được cấp phép bởi Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
Các phương thức async của tôi đang chờ các phương thức async khác. Tôi KHÔNG trang trí bất kỳ awaitcuộc gọi của tôi với ConfigureAwait(false). Tôi đã thử sử dụng AsyncHelper.RunSyncđể gọi một hàm async từ Application_Start()hàm trong Global.asax và nó dường như hoạt động. Điều này có nghĩa là điều đó AsyncHelper.RunSyncđáng tin cậy không phải là vấn đề bế tắc "trở lại bối cảnh của người gọi" mà tôi đã đọc về nơi khác trong bài đăng này?
Bob.at.Ấn Độ. Sức khỏe

1
@ Bob.at.SBS phụ thuộc vào những gì bạn mã. Nó không đơn giản như thể tôi sử dụng mã này là tôi an toàn . Đây là cách rất tối thiểu và bán an toàn để chạy các lệnh async một cách đồng bộ, nó có thể dễ dàng được sử dụng không phù hợp để gây ra bế tắc.
Erik Philips

1
Cảm ơn. 2 câu hỏi tiếp theo: 1) Bạn có thể đưa ra một ví dụ về một cái gì đó mà phương thức async muốn tránh sẽ gây ra bế tắc và 2) là những bế tắc trong bối cảnh này thường phụ thuộc vào thời gian không? Nếu nó hoạt động trong thực tế, tôi vẫn có thể gặp bế tắc phụ thuộc thời gian ẩn trong mã của mình chứ?
Bob.at.Ấn Độ. Sức khỏe

@ Bob.at.SBS Tôi khuyên bạn nên đặt câu hỏi bằng cách sử dụng nút Hỏi Câu hỏi ở trên cùng bên phải. Bạn có thể bao gồm một liên kết đến câu hỏi này hoặc câu trả lời trong câu hỏi của bạn như một tài liệu tham khảo.
Erik Philips

1
@ Bob.at ... mã do Erik cung cấp hoạt động hoàn hảo trong Asp. net mvc5 và EF6, nhưng không phải khi tôi thử bất kỳ giải pháp nào khác (ConfigureAwait (false) .GetAwaiter (). GetResult () hoặc .result) treo hoàn toàn ứng dụng web của tôi
LeonardoX

151

async Main hiện là một phần của C # 7.2 và có thể được bật trong cài đặt bản dựng nâng cao của dự án.

Đối với C # <7.2, cách chính xác là:

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


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

Bạn sẽ thấy điều này được sử dụng trong rất nhiều tài liệu của Microsoft, ví dụ: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- đăng ký chủ đề


11
Tôi không biết TẠI SAO ai đó đã bỏ phiếu này. Nó hiệu quả tuyệt vời đối với tôi. Nếu không có sửa chữa này, tôi sẽ phải tuyên truyền ASYCH MỌI NƠI.
Tù nhân ZERO

11
Tại sao điều này tốt hơn MainAsync().Wait()?
nghiền nát

8
Tôi đồng ý. Bạn chỉ cần MainAsync (). Đợi () thay vì tất cả điều này.
Hajjat

8
@crush Tôi đã mô tả làm thế nào điều này có thể tránh được một số bế tắc. Trong một số trường hợp, việc gọi .Wait () từ luồng UI hoặc asp.net gây ra bế tắc. bế tắc không đồng bộ
David

6
@ClintB: Bạn tuyệt đối không nên làm điều này trong ASP.NET Core. Các ứng dụng web đặc biệt dễ bị thiếu luồng và mỗi khi bạn thực hiện việc này, bạn sẽ kéo một luồng từ nhóm mà nếu không sẽ được sử dụng để phục vụ yêu cầu. Nó ít gây rắc rối hơn cho các ứng dụng trên máy tính để bàn / thiết bị di động vì theo truyền thống họ là người dùng đơn lẻ.
Chris Pratt

52
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

}

Bạn đọc từ khóa 'await' là "bắt đầu tác vụ dài này, sau đó trả lại quyền điều khiển cho phương thức gọi". Khi tác vụ chạy dài được thực hiện, nó sẽ thực thi mã sau nó. Mã sau khi chờ đợi tương tự như mã được sử dụng là phương thức CallBack. Sự khác biệt lớn là luồng logic không bị gián đoạn, giúp viết và đọc dễ dàng hơn nhiều.


15
Waitkết thúc ngoại lệ và có khả năng bế tắc.
Stephen Cleary

Tôi nghĩ rằng nếu bạn gọi một phương thức async mà không sử dụng await, nó sẽ được thực thi đồng bộ. Ít nhất điều đó làm việc cho tôi (mà không gọi myTask.Wait). Trên thực tế, tôi đã có một ngoại lệ khi tôi cố gắng gọi myTask.RunSynchronously()vì nó đã được thực thi!
sợ

2
Tôi thích câu trả lời này. Bình luận tốt để chỉnh sửa, nhỏ, và thanh lịch. Cảm ơn bạn đã đóng góp! Tôi vẫn học đồng thời, vì vậy mọi thứ đều có ích :)
kayleeFrye_onDeck

2
Câu trả lời này có còn hiệu quả như ngày hôm nay không? Tôi vừa thử nó trong một dự án MVC Razor và ứng dụng chỉ dừng lại khi truy cập .Result.
Mã hóa đã qua

8
@TrueBlueAussie Đó là bế tắc bối cảnh đồng bộ hóa. Mã không đồng bộ mã của bạn trở lại bối cảnh đồng bộ hóa, nhưng điều đó bị chặn bởi Resultcuộc gọi vào thời điểm đó, vì vậy nó không bao giờ đến đó. Và Resultkhông bao giờ kết thúc, bởi vì nó đang chờ đợi một người đang chờ Resultkết thúc, về cơ bản: D
Luaan 23/2/2015

40

Tôi không chắc chắn 100%, nhưng tôi tin rằng kỹ thuật được mô tả trong blog này sẽ hoạt động trong nhiều trường hợp:

Do đó, bạn có thể sử dụng task.GetAwaiter().GetResult()nếu bạn muốn trực tiếp gọi logic lan truyền này.


6
Giải pháp A trong câu trả lời của Stephen Cleary ở trên sử dụng phương pháp này. Xem nguồn WaitAndUnwrapException .
orad

bạn có cần sử dụng GetResult () nếu hàm bạn đang gọi là void hoặc task không? Ý tôi là nếu bạn không muốn nhận lại bất kỳ kết quả nào
batmaci

Có, nếu không nó sẽ không chặn cho đến khi hoàn thành nhiệm vụ. Ngoài ra, thay vì gọi GetAwaiter (). GetResult () bạn có thể gọi .Wait ()
NStuke

1
Đó là phần "nhiều hoàn cảnh". Nó phụ thuộc vào mô hình luồng tổng thể và những luồng khác đang làm gì để xác định xem có nguy cơ bế tắc hay không.
NStuke

GetAwaiter (). GetResult () vẫn có thể gây ra bế tắc. Nó chỉ mở ra ngoại lệ thành một điều hợp lý hơn.
nawfal

25

Tuy nhiên, có một giải pháp tốt hoạt động trong (hầu hết: xem bình luận) mọi tình huống: một máy bơm thông báo đặc biệt (SyncizationContext).

Chuỗi cuộc gọi sẽ bị chặn như mong đợi, trong khi vẫn đảm bảo rằng tất cả các liên tục được gọi từ chức năng async không bị bế tắc vì chúng sẽ được sắp xếp thành Đồng bộ hóa quảng cáo (bơm thông báo) đang chạy trên chuỗi cuộc gọi.

Mã của trình trợ giúp bơm tin nhắn ad-hoc:

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

Sử dụng:

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

Mô tả chi tiết hơn về bơm async có sẵn ở đây .


Bối cảnh ngoại lệ và AsyncPump stackoverflow.com/questions/23161693/iêu
PreguntonCojoneroCabrón

Điều này không hoạt động trong một kịch bản Asp.net, vì bạn có thể mất ngẫu nhiên httpContext.C hiện tại.
Josh Mouch

12

Để bất cứ ai chú ý đến câu hỏi này nữa ...

Nếu bạn nhìn vào Microsoft.VisualStudio.Services.WebApicó một lớp được gọi TaskExtensions. Trong lớp đó, bạn sẽ thấy phương thức mở rộng tĩnhTask.SyncResult() , giống như hoàn toàn chỉ chặn luồng cho đến khi tác vụ trở lại.

Trong nội bộ, nó gọi task.GetAwaiter().GetResult()là khá đơn giản, tuy nhiên, nó quá tải để làm việc trên bất kỳ asyncphương pháp nào quay trở lại Task, Task<T>hoặc Task<HttpResponseMessage>... đường cú pháp, em bé ... cha có một chiếc răng ngọt ngào.

Có vẻ như ...GetAwaiter().GetResult()là cách chính thức của MS để thực thi mã async trong ngữ cảnh chặn. Có vẻ để làm việc rất tốt cho trường hợp sử dụng của tôi.


3
Bạn đã cho tôi tại "giống như hoàn toàn chỉ là khối".
Dawood ibn Kareem

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Hoặc sử dụng cái này:

var result=result.GetAwaiter().GetResult().AccessToken

6

Bạn có thể gọi bất kỳ phương thức không đồng bộ nào từ mã đồng bộ, nghĩa là, cho đến khi bạn cần awaitbật chúng, trong trường hợp đó chúng cũng phải được đánh dấu async.

Như nhiều người đang đề xuất ở đây, bạn có thể gọi Wait () hoặc Kết quả về tác vụ kết quả trong phương thức đồng bộ của bạn, nhưng sau đó bạn kết thúc bằng một cuộc gọi chặn trong phương thức đó, loại này đánh bại mục đích của async.

Tôi thực sự không thể thực hiện phương thức của mình asyncvà bạn không muốn khóa phương thức đồng bộ, sau đó bạn sẽ phải sử dụng phương thức gọi lại bằng cách chuyển nó làm tham số cho phương thức ContinWith trong nhiệm vụ.


5
Sau đó, điều đó sẽ không được gọi phương thức một cách đồng bộ bây giờ phải không?
Jeff Mercado

2
Theo tôi hiểu, câu hỏi là bạn có thể gọi một phương thức async từ phương thức không đồng bộ. Điều này không có nghĩa là phải gọi phương thức async theo cách chặn.
sở2

Xin lỗi, "chúng cũng phải được đánh dấu async" đã thu hút sự chú ý của tôi khỏi những gì bạn đang nói.
Jeff Mercado

Nếu tôi không thực sự quan tâm đến tính không đồng bộ, tôi có thể gọi nó theo cách này không (và khả năng của những bế tắc trong các trường hợp ngoại lệ mà Stephen Cleary tiếp tục cằn nhằn là gì?) Tôi có một số phương pháp thử nghiệm (phải được thực hiện đồng bộ) kiểm tra các phương pháp không đồng bộ. Tôi phải đợi kết quả trước khi tiếp tục, vì vậy tôi có thể kiểm tra kết quả của phương pháp không đồng bộ.
sợ

6

Tôi biết tôi đến muộn. Nhưng trong trường hợp ai đó như tôi muốn giải quyết vấn đề này một cách gọn gàng, dễ dàng và không phụ thuộc vào thư viện khác.

Tôi tìm thấy đoạn mã sau từ Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

sau đó bạn có thể gọi nó như thế này

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
Điều này trông giống hệt như câu trả lời ở trên Tôi đang thiếu một cái gì đó
inlokesh

2

Sau nhiều giờ thử các phương pháp khác nhau, với ít nhiều thành công, đây là những gì tôi đã kết thúc. Nó không kết thúc trong bế tắc trong khi nhận được kết quả và nó cũng nhận và ném ngoại lệ ban đầu và không phải là ngoại lệ.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

1
Hoạt động với tác vụ trả về.GetAwaiter (). GetResult ();
Mỗi

có, nhưng những gì về ngoại lệ ban đầu?
Jiří Herník

.Result tôi nghĩ về cơ bản giống như .GetAwaiter (). GetResult ()
Per G

-2

Nó có thể được gọi từ một luồng mới (KHÔNG phải từ nhóm luồng!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

Các phương thức async của windows này có một phương thức nhỏ tiện lợi gọi là AsTask (). Bạn có thể sử dụng điều này để có phương thức tự trả về như một tác vụ để bạn có thể gọi Wait () theo cách thủ công trên nó.

Ví dụ: trên ứng dụng Windows Phone 8 Silverlight, bạn có thể thực hiện các thao tác sau:

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

Hi vọng điêu nay co ich!


-4

Nếu bạn muốn chạy nó Đồng bộ hóa

MethodAsync().RunSynchronously()

3
Phương pháp này được thiết kế để bắt đầu các nhiệm vụ lạnh. Thông thường các phương thức async trả về một tác vụ nóng, nói cách khác, một tác vụ đã bắt đầu. kêu gọi RunSynchronously()một kết quả nhiệm vụ nóng đến một InvalidOperationException. Hãy thử với mã này:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias

-5
   //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;

2
Tạo ra bế tắc. Tốt hơn nên xóa câu trả lời.
PreguntonCojoneroCabrón

Nhiệm vụ.Run (() => SaveAssetDataAsDraft ()). Kết quả; - không tạo ra bế tắc
Anubis
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.