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


231

Tôi có một asyncphương pháp:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

Tôi cần gọi phương thức này từ một phương thức đồng bộ.

Làm thế nào tôi có thể làm điều này mà không phải sao chép GenerateCodeAsyncphương thức để nó hoạt động đồng bộ?

Cập nhật

Tuy nhiên, không có giải pháp hợp lý được tìm thấy.

Tuy nhiên, tôi thấy rằng HttpClientđã thực hiện mô hình này

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}


1
Tôi đã hy vọng cho một giải pháp đơn giản hơn, nghĩ rằng asp.net xử lý việc này dễ dàng hơn nhiều so với việc viết quá nhiều dòng mã
Catalin

Tại sao không nắm lấy mã async? Lý tưởng nhất là bạn muốn nhiều mã async hơn, không phải ít hơn.
Paulo Morgado

54
[Tại sao không nắm lấy mã async?] Ha, có thể chính xác là do một người đang nắm lấy mã async mà họ cần giải pháp này khi các phần lớn của dự án được chuyển đổi! Bạn không thể xây dựng lại Rome trong một ngày.
Nicholas Petersen

1
@NicholasPeteren đôi khi thư viện của bên thứ 3 có thể buộc bạn phải làm điều này. Ví dụ xây dựng các thông điệp động trong phương thức WithMessage từ FluentValidation. Không có API async cho điều này do thiết kế thư viện - Quá tải WithMessage là tĩnh. Các phương thức khác để truyền đối số động cho WithMessage là lạ.
H.Wojtowicz

Câu trả lời:


278

Bạn có thể truy cập thuộc Resulttính của tác vụ, điều này sẽ khiến luồng của bạn bị chặn cho đến khi có kết quả:

string code = GenerateCodeAsync().Result;

Lưu ý: Trong một số trường hợp, điều này có thể dẫn đến bế tắc: Cuộc gọi của bạn để Resultchặn luồng chính, do đó ngăn phần còn lại của mã async thực thi. Bạn có các tùy chọn sau để đảm bảo rằng điều này không xảy ra:

Điều này không có nghĩa là bạn chỉ nên thêm .ConfigureAwait(false)vào một cách vô thức sau tất cả các cuộc gọi không đồng bộ của bạn! Để biết phân tích chi tiết về lý do và thời điểm bạn nên sử dụng .ConfigureAwait(false), hãy xem bài đăng trên blog sau:


33
Nếu cách gọi resultrủi ro một bế tắc, sau đó khi nó an toàn để có được kết quả? Có phải mọi cuộc gọi không đồng bộ đều yêu cầu Task.Runhay ConfigureAwait(false)không?
Robert Harvey

4
Không có "luồng chính" trong ASP.NET (không giống như ứng dụng GUI), nhưng vẫn có thể bế tắc vì cách AspNetSynchronizationContext.Post nối tiếp các Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
chuỗi

4
@RobertHarvey: Nếu bạn không kiểm soát được việc triển khai phương thức async mà bạn đang chặn, thì có, bạn nên bọc lại Task.Runđể giữ an toàn. Hoặc sử dụng một cái gì đó như WithNoContextđể giảm chuyển đổi chủ đề dư thừa.
chơi

10
LƯU Ý: Cuộc gọi .Resultvẫn có thể bế tắc nếu người gọi ở trên nhóm luồng. Lấy một kịch bản trong đó Thread Pool có kích thước 32 và 32 tác vụ đang chạy và Wait()/Resultchờ đợi trong một nhiệm vụ thứ 33 chưa được lên lịch mà muốn chạy trên một trong các luồng chờ.
Chiến tranh

55

Bạn sẽ nhận được Waititer ( GetAwaiter()) và kết thúc chờ đợi để hoàn thành nhiệm vụ không đồng bộ ( GetResult()).

string code = GenerateCodeAsync().GetAwaiter().GetResult();

38
Chúng tôi đã chạy vào bế tắc bằng cách sử dụng giải pháp này. Được cảnh báo.
Oliver

6
MSDNTask.GetAwaiter : Phương pháp này dành cho sử dụng trình biên dịch thay vì sử dụng trong mã ứng dụng.
foka

Tôi vẫn gặp lỗi Hộp thoại bật lên (trái với ý muốn của tôi), với các nút 'Chuyển sang' hoặc 'Thử lại'. tuy nhiên, cuộc gọi thực sự thực hiện và trả về với một phản hồi thích hợp.
Jonathan Hansen

30

Bạn sẽ có thể hoàn thành việc này bằng cách sử dụng các đại biểu, biểu thức lambda

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }

Đoạn mã này sẽ không biên dịch. Kiểu trả về từ Task.Run là Nhiệm vụ. Xem blog MSDN này để được giải thích đầy đủ.
Xuất hiện

5
Cảm ơn đã chỉ ra, có nó trả về loại Nhiệm vụ. Việc thay thế "chuỗi sCode" thành Nhiệm vụ <chuỗi> hoặc var sCode sẽ giải quyết nó. Thêm một mã biên dịch đầy đủ cho dễ dàng.
Faiyaz

20

Tôi cần gọi phương thức này từ một phương thức đồng bộ.

Điều đó có thể với GenerateCodeAsync().Resulthoặc GenerateCodeAsync().Wait(), như câu trả lời khác cho thấy. Điều này sẽ chặn luồng hiện tại cho đến khi GenerateCodeAsynchoàn thành.

Tuy nhiên, câu hỏi của bạn được gắn thẻ và bạn cũng để lại bình luận:

Tôi đã hy vọng cho một giải pháp đơn giản hơn, nghĩ rằng asp.net xử lý việc này dễ dàng hơn nhiều so với việc viết quá nhiều dòng mã

Quan điểm của tôi là, bạn không nên chặn phương thức không đồng bộ trong ASP.NET. Điều này sẽ làm giảm khả năng mở rộng của ứng dụng web của bạn và có thể tạo ra sự bế tắc (khi phần awaittiếp theo bên trong GenerateCodeAsyncđược đăng lên AspNetSynchronizationContext). Sử dụngTask.Run(...).Result để giảm tải một cái gì đó cho một chuỗi pool và sau đó chặn sẽ làm tổn hại đến khả năng mở rộng hơn nữa, vì nó phải chịu thêm +1 luồng để xử lý một yêu cầu HTTP đã cho.

ASP.NET có hỗ trợ tích hợp cho các phương thức không đồng bộ, thông qua các bộ điều khiển không đồng bộ (trong ASP.NET MVC và Web API) hoặc trực tiếp thông qua AsyncManagerPageAsyncTasktrong ASP.NET cổ điển. Bạn nên sử dụng nó. Để biết thêm chi tiết, kiểm tra câu trả lời này .


Tôi đang ghi đè SaveChanges()phương thức DbContextvà ở đây tôi đang gọi các phương thức async, vì vậy thật không may, bộ điều khiển async sẽ không giúp tôi trong tình huống này
Catalin

3
@RaraituL, nói chung, bạn không trộn lẫn mã không đồng bộ và mã đồng bộ hóa, chọn mô hình euther. Bạn có thể thực hiện cả hai SaveChangesAsyncSaveChanges, chỉ cần đảm bảo rằng chúng không được gọi cả hai trong cùng một dự án ASP.NET.
noseratio

4
Chẳng hạn, tất cả các .NET MVCbộ lọc đều hỗ trợ mã không đồng bộ IAuthorizationFilter, vì vậy tôi không thể sử dụng asyncmọi cách
Catalin

3
@Noseratio đó là một mục tiêu không thực tế. Có quá nhiều thư viện với mã không đồng bộ và mã đồng bộ cũng như các tình huống chỉ sử dụng một mô hình. Ví dụ, MVC ActionFilters không hỗ trợ mã không đồng bộ.
Justin Skiles

9
@Noserato, câu hỏi là về cách gọi phương thức không đồng bộ từ đồng bộ. Đôi khi bạn không thể thay đổi API bạn thực hiện. Giả sử bạn triển khai một số giao diện đồng bộ từ khung "A" của bên thứ 3 (bạn không thể viết lại khung theo cách không đồng bộ) nhưng thư viện của bên thứ 3 "B" bạn đang cố sử dụng trong triển khai của mình chỉ không đồng bộ. Sản phẩm kết quả cũng là thư viện và có thể được sử dụng ở bất cứ đâu, kể cả ASP.NET, v.v.
dimzon

19

Microsoft Identity có các phương thức mở rộng gọi các phương thức async một cách đồng bộ. Ví dụ: có phương thức GenerateUserIdentityAsync () và bằng CreatIdentity () bằng nhau

Nếu bạn nhìn vào UserManagerExtensions.CreateIdentity () thì nó trông như thế này:

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

Bây giờ hãy xem AsyncHelper.RunSync làm gì

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

Vì vậy, đây là trình bao bọc của bạn cho phương thức async. Và vui lòng không đọc dữ liệu từ Kết quả - nó có khả năng sẽ chặn mã của bạn trong ASP.

Có một cách khác - đó là nghi ngờ đối với tôi, nhưng bạn cũng có thể xem xét nó

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();

3
Bạn nghĩ gì về vấn đề với cách thứ hai mà bạn đã đề xuất?
David Clarke

@DavidClarke có lẽ là vấn đề an toàn của luồng khi truy cập một biến không dễ bay hơi từ nhiều luồng mà không có khóa.
Theodor Zoulias

9

Để ngăn chặn bế tắc, tôi luôn cố gắng sử dụng Task.Run() khi tôi phải gọi một phương thức async một cách đồng bộ mà @Heinzi đề cập.

Tuy nhiên, phương thức phải được sửa đổi nếu phương thức async sử dụng tham số. Ví dụ Task.Run(GenerateCodeAsync("test")).Resultđưa ra lỗi:

Đối số 1: không thể chuyển đổi từ ' System.Threading.Tasks.Task<string>' sang 'System.Action'

Điều này có thể được gọi như thế này thay vào đó:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;

6

Hầu hết các câu trả lời trên chủ đề này là phức tạp hoặc sẽ dẫn đến bế tắc.

Phương pháp sau rất đơn giản và nó sẽ tránh được bế tắc vì chúng tôi đang chờ nhiệm vụ hoàn thành và chỉ sau đó mới nhận được kết quả của nó-

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;

Hơn nữa, đây là một tài liệu tham khảo cho bài viết MSDN nói về chính xác điều tương tự- https://bloss.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- trong bối cảnh chính /


1

Tôi thích một cách tiếp cận không chặn:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
            While Not aw1.IsCompleted
                Application.DoEvents()
            End While

0

Vâng, tôi đang sử dụng phương pháp này:

    private string RunSync()
    {
        var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
        if (task.IsFaulted && task.Exception != null)
        {
            throw task.Exception;
        }

        return task.Result;
    }

-1

Một cách khác có thể là nếu bạn muốn đợi cho đến khi nhiệm vụ kết thúc:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;

1
Điều đó hoàn toàn sai. Khi AllAll cũng trả về một Nhiệm vụ mà bạn không chờ đợi.
Robert Schmidt

-1

BIÊN TẬP:

Tác vụ có phương thức Wait, Task.Wait (), chờ "lời hứa" giải quyết và sau đó tiếp tục, do đó hiển thị đồng bộ. thí dụ:


async Task<String> MyAsyncMethod() { ... }

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
Vui lòng giải thích về câu trả lời của bạn. Nó được sử dụng như thế nào? Làm thế nào cụ thể nó giúp trả lời câu hỏi?
Scratte

-2

Nếu bạn có một phương thức async được gọi là " RefreshList " thì bạn có thể gọi phương thức async đó từ một phương thức không đồng bộ như bên dưới.

Task.Run(async () => { await RefreshList(); });
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.