Zaman uyumsuz yöntemi senkronize olarak çağırmak


231

Bir asyncyöntem var:

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

Senkronize bir yöntemden bu yöntemi çağırmak gerekiyor.

GenerateCodeAsyncEşzamanlı olarak çalışması için yöntemi çoğaltmak zorunda kalmadan bunu nasıl yapabilirim ?

Güncelleme

Ancak makul bir çözüm bulunamadı.

Ancak, HttpClientzaten bu kalıbı uyguladığını görüyorum

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

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


1
Asp.net'in bu kadar çok kod satırı yazmaktan çok daha kolay işlediğini düşünerek daha basit bir çözüm umuyordum
Catalin

Neden sadece eşzamansız kodu kabul etmiyorsunuz? İdeal olarak daha az async kodu istemezsiniz.
Paulo Morgado

54
[Neden async kodunu kucaklamıyorsunuz?] Ha, bunun nedeni, projenin büyük bölümleri dönüştürüldükçe bu çözüme ihtiyaç duydukları async kodunu kucaklaması olabilir! Roma'yı bir günde yeniden inşa edemezsin.
Nicholas Petersen

1
@NicholasPetersen bazen 3. taraf kütüphanesi sizi bunu yapmaya zorlayabilir. FluentValidation'dan WithMessage yönteminde dinamik mesaj oluşturma örneği. Kitaplık tasarımı nedeniyle bunun için zaman uyumsuz API yoktur - WithMessage aşırı yüklemeleri statiktir. Dinamik bağımsız değişkenleri WithMessage'a aktarmanın diğer yöntemleri tuhaftır.
H.Wojtowicz

Yanıtlar:


278

ResultGörevin özelliğine erişebilirsiniz; bu, sonuç elde edilinceye kadar iş parçacığınızın engellenmesine neden olur:

string code = GenerateCodeAsync().Result;

Not: Bazı durumlarda bu bir kilitlenmeye neden olabilir: ResultAna iş parçacığını engelleme çağrınız , böylece zaman uyumsuz kodun geri kalanının yürütülmesini engeller. Bunun olmadığından emin olmak için aşağıdaki seçenekleriniz vardır:

Bu ,.ConfigureAwait(false) tüm zaman uyumsuz çağrılarınızdan sonra dikkatsizce eklemeniz gerektiği anlamına gelmez ! Neden ve ne zaman kullanmanız gerektiğiyle ilgili ayrıntılı bir analiz .ConfigureAwait(false)için aşağıdaki blog yayınına bakın:


33
Çağıran Eğer resultbir kilitlenme riski, daha sonra zaman olduğu o sonuç almak için güvenli? Her zaman uyumsuz çağrı gerektiriyor mu Task.Runveya ConfigureAwait(false)?
Robert Harvey

4
ASP.NET'te (GUI uygulamasından farklı olarak) "ana iş parçacığı" yoktur, ancak zaman uyumsuz AspNetSynchronizationContext.PostTask newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
süreleri

4
@RobertHarvey: Engellediğiniz zaman uyumsuz yöntemin uygulanması üzerinde hiçbir denetiminiz yoksa, evet, Task.Rungüvende kalmak için onu sarmalısınız . Veya WithNoContextgereksiz iş parçacığı geçişini azaltmak gibi bir şey kullanın .
noseratio

10
NOT: Arayan kişi .Resultiş parçacığı havuzundaysa arama yine de kilitlenebilir. İş Parçacığı Havuzu 32 ve 32 boyutunda görevlerin çalıştığı ve Wait()/Resultbekleyen iş parçacıklarından birinde çalıştırmak isteyen henüz planlanmamış bir 33. görevde bekleyen bir senaryo alın .
Warty

55

Awaiter ( GetAwaiter()) yöntemini almalı ve eşzamansız görevin ( GetResult()) tamamlanmasını beklemelisiniz .

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

38
Bu çözümü kullanarak çıkmazlarla karşılaştık. Dikkatli olun.
Oliver

6
MSDNTask.GetAwaiter : Bu yöntem, uygulama kodunda kullanmak yerine derleyici kullanımı için tasarlanmıştır.
foka

Hala 'Geçiş' veya 'Yeniden Dene' düğmeleriyle İletişim penceresi açılır (irademe karşı) hatası alıyorum. ancak, çağrı aslında yürütülür ve uygun bir yanıtla geri döner.
Jonathan Hansen

30

Delegeler, lambda ifadesi kullanarak bunu yapabilmelisiniz

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

Bu snippet derlenmeyecek. Görev.Run öğesinin dönüş türü Görev'dir. Tam açıklama için bu MSDN bloguna bakın .
Appetere

5
İşaret ettiğiniz için teşekkürler, evet Görev türünü döndürür. "String sCode" yerine Görev <string> veya var sCode kullanılması bunu çözmelidir. Kolaylık için tam bir derleme kodu ekleme.
Faiyaz

20

Bu yöntemi eşzamanlı bir yöntemden çağırmak gerekir.

Diğer cevabın önerdiği gibi GenerateCodeAsync().Resultveya ile mümkündür GenerateCodeAsync().Wait(). Bu işlem geçerli iş parçacığını engelleyene kadarGenerateCodeAsync tamamlanıncaya .

Ancak, sorunuz etiketlendi ve ayrıca yorumu da bıraktınız:

Asp.net'in bu kadar çok kod satırı yazmaktan çok daha kolay işlediğini düşünerek daha basit bir çözüm umuyordum

Demek istediğim , ASP.NET'te eşzamansız bir yöntemi engellememelisiniz . Bu, web uygulamanızın ölçeklenebilirliğini azaltır ve bir kilitlenme (await içeride devam GenerateCodeAsyncgönderildiğinde AspNetSynchronizationContext). Kullanılması Task.Run(...).Resultverilen bir HTTP isteği işlemek için 1 daha parçacığı doğurur bir havuz iplik ve sonra bloğa boşaltma şey, daha ölçeklenebilirlik bile zarar verir.

ASP.NET üzerinden doğrudan asenkron kontrolörleri aracılığıyla (Web API ASP.NET MVC ve) ya asenkron yöntemleri için yerleşik destek veya etmiştir AsyncManagerve PageAsyncTaskklasik ASP.NET içinde. Kullanmalısın. Daha fazla ayrıntı için bu cevabı kontrol edin .


Ben üzerine yazma SaveChanges()yöntemi DbContext, ve ben burada zaman uyumsuz yöntemleri çağırıyorum, bu yüzden ne yazık ki zaman uyumsuz denetleyici bana bu durumda yardımcı olmaz
Catalin

3
@RaraituL, genel olarak, zaman uyumsuz ve senkronizasyon kodunu karıştırmaz, öten modeli seçersiniz. Her ikisini de uygulayabilir SaveChangesAsyncve SaveChangesaynı ASP.NET projesinde her ikisinin de çağrılmadığından emin olabilirsiniz.
noseratio

4
.NET MVCÖrneğin IAuthorizationFilter, tüm filtreler asenkron kodu desteklemez , bu yüzden asynctüm yolu kullanamam
Catalin

3
@Noseratio bu gerçekçi olmayan bir hedef. Asenkron ve senkron kodlu çok fazla kütüphane ve sadece bir modelin kullanılmasının mümkün olmadığı durumlar var. MVC ActionFilters, örneğin eşzamansız kodu desteklemez.
Justin Skiles

9
@Noserato, soru senkronize olmayan zaman uyumsuz yöntemi çağırmak hakkında. Bazen uyguladığınız API'yı değiştiremezsiniz. Diyelim ki bazı üçüncü taraf çerçeve "A" (çerçeveyi eşzamansız olarak yeniden yazamazsınız) ancak uygulamanızda kullanmaya çalıştığınız üçüncü taraf kitaplığı "B" yalnızca eşzamansızdır. Ayrıca ortaya çıkan ürün de kütüphanedir ve ASP.NET vb.
Dahil

19

Microsoft Identity, zaman uyumsuz yöntemleri eşzamanlı olarak çağıran uzantı yöntemleri vardır. Örneğin GenerateUserIdentityAsync () yöntemi ve eşit CreateIdentity () vardır

UserManagerExtensions.CreateIdentity () öğesine bakarsanız şöyle görünür:

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

Şimdi AsyncHelper.RunSync'in ne yaptığını görelim

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

Yani, bu zaman uyumsuz yöntem için sarıcı. Ve lütfen Sonuçtan veri okumayın - ASP'deki kodunuzu engeller.

Başka bir yol var - bu benim için şüpheli, ama sen de düşünebilirsin

  Result r = null;

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

3
Önerdiğiniz ikinci yolla ilgili sorun olarak ne düşünüyorsunuz?
David Clarke

@DavidClarke, büyük olasılıkla kilitsiz birden çok iş parçacığından geçici olmayan bir değişkene erişmenin iş parçacığı güvenliği sorunu.
Theodor Zoulias

9

Kilitlenmeleri önlemek için her zaman kullanmaya çalışıyorum Task.Run() , @Heinzi'den bahsettiği zaman zaman uyumsuz bir yöntemi çağırmak denerim.

Ancak, zaman uyumsuzluk yöntemi parametreler kullanıyorsa yöntemin değiştirilmesi gerekir. Örneğin Task.Run(GenerateCodeAsync("test")).Resulthatayı verir:

Bağımsız değişken 1: " System.Threading.Tasks.Task<string>" den "Sistem'e" dönüştürülemiyor

Bunun yerine şöyle denilebilir:

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

6

Bu konudaki cevapların çoğu ya karmaşıktır ya da kilitlenme ile sonuçlanır.

Aşağıdaki yöntem basittir ve kilitlenmeyi önleyecektir, çünkü görevin bitmesini ve ancak sonucunu almayı bekliyoruz.

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

Ayrıca, tam olarak aynı şeyden bahseden MSDN makalesine bir referans - https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- in ana bağlamda /


1

Engellemeyen bir yaklaşımı tercih ederim:

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

0

Bu yaklaşımı kullanıyorum:

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

        return task.Result;
    }

-1

Diğer yol, görev bitene kadar beklemek istiyorsanız olabilir:

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

1
Bu tamamen yanlış. WhenAll ayrıca beklemediğiniz bir Görev döndürür.
Robert Schmidt

-1

DÜZENLE:

Görevin "vaat" in çözülmesini ve ardından devam etmesini bekleyerek eşzamanlı hale getiren Bekleme yöntemi olan Task.Wait () vardır. misal:


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

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
Lütfen cevabınızı detaylandırın. Nasıl kullanılır? Soruyu cevaplamak özellikle nasıl yardımcı olur?
Scratte

-2

" RefreshList " adında bir async yönteminiz varsa, bu async yöntemini aşağıdaki gibi asenkron olmayan bir yöntemden çağırabilirsiniz.

Task.Run(async () => { await RefreshList(); });
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.