Alıcıdan veya ayarlayıcıdan zaman uyumsuz yöntem nasıl çağrılır?


223

C # 'da bir alıcı veya ayarlayıcıdan bir zaman uyumsuz yöntem çağırmanın en zarif yolu ne olurdu?

İşte kendimi açıklamaya yardımcı olacak bazı sahte kodlar.

async Task<IEnumerable> MyAsyncMethod()
{
    return await DoSomethingAsync();
}

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}

4
Sorum neden olurdu. Bir mülkün, tipik olarak çok az (veya en azından çok hızlı) iş yapması gerektiğinden, alan gibi bir şeyi taklit etmesi gerekir. Uzun süredir devam eden bir mülkünüz varsa, onu bir yöntem olarak yazmak çok daha iyidir, böylece arayan bunun daha karmaşık bir çalışma gövdesi olduğunu bilir.
James Michael Hare

@James: Bu kesinlikle doğru - ve bu yüzden bunun CTP'de açıkça desteklenmediğinden şüpheleniyorum. Bununla birlikte, Task<T>hemen geri dönecek, normal özellik semantiğine sahip olan ve yine de şeylerin gerektiği gibi eşzamansız olarak işlenmesine izin veren tip özelliğini her zaman yapabilirsiniz .
Reed Copsey

17
@James İhtiyacım Mvvm ve Silverlight kullanmaktan kaynaklanıyor. Verilerin yüklenmesinin tembel olarak yapıldığı bir özelliğe bağlanabilmek istiyorum. Kullandığım ComboBox uzantı sınıfı, bağlantının InitializeComponent () aşamasında gerçekleşmesini gerektirir, ancak gerçek veri yükü daha sonra gerçekleşir. Mümkün olduğunca az kodla başarmaya çalışırken, getter ve async mükemmel bir kombinasyon gibi hissediyor.
Doguhan Uluca


James ve Reed, Görünüşe göre her zaman uç durumlar var. WCF durumunda, bir mülke yerleştirilen verilerin doğru olduğunu ve şifreleme / şifre çözme kullanılarak doğrulanması gerektiğini doğrulamak istedim. Şifre çözme için kullandığım işlevler, üçüncü taraf bir satıcıdan zaman uyumsuz işlev kullanıyor. (ÇOK DEĞİL BURAYA YAPABİLİRİM).
RashadRivera

Yanıtlar:


211

C # ' da özelliklere izin verilmesinin teknik bir nedeni yoktur async. Amaçlı bir tasarım kararıdır, çünkü "asenkron özellikler" bir oksimorondur.

Özellikler geçerli değerleri döndürmelidir; arka plan işlemlerini başlatmamalıdırlar.

Genellikle, birisi "eşzamansız özellik" istediğinde, gerçekten istediği şey bunlardan biridir:

  1. Bir değer döndüren zaman uyumsuz bir yöntem. Bu durumda, özelliği bir asyncyönteme değiştirin.
  2. Veri bağlamada kullanılabilen, ancak eşzamansız olarak hesaplanması / alınması gereken bir değer. Bu durumda, asynciçeren nesne için bir fabrika yöntemi kullanın veya bir async InitAsync()yöntem kullanın . Veriye bağlı değer default(T), değer hesaplanana / alınana kadar olacaktır .
  3. Oluşturulması pahalı olan ancak gelecekte kullanılmak üzere önbelleğe alınması gereken bir değer. Bu durumda, kullanmak AsyncLazy benim blogdan veya AsyncEx kütüphanesine . Bu size awaityetenekli bir mülk verecektir .

Güncelleme: En son "zaman uyumsuz OOP" blog yayınlarımdan birinde zaman uyumsuz özellikleri ele aldım.


2. maddede imho, özelliğin ayarının temel verileri yeniden başlatması gerektiği normal senaryoyu dikkate almazsınız (yalnızca yapıcıda değil). Nito AsyncEx'i kullanmanın veya kullanmanın dışında başka bir yolu var mı Dispatcher.CurrentDispatcher.Invoke(new Action(..)?
Gerard

@Gerard: Nokta (2) 'nin bu durumda neden işe yaramadığını anlamıyorum. Sadece uygulayın INotifyPropertyChangedve eski değerin döndürülmesini isteyip istemediğinize veya default(T)eşzamansız güncelleme uçuştayken karar verin .
Stephen Cleary

1
@Stephan: Tamam, ancak ayarlayıcıda async yöntemini çağırdığımda CS4014 "beklenmedik" uyarısı alıyorum (veya yalnızca Framework 4.0'da mı?). Böyle bir durumda bu uyarıyı bastırmanızı tavsiye eder misiniz?
Gerard

@Gerard: Benim ilk öneri kullanmak olacaktır NotifyTaskCompletionbenim AsyncEx projesinden . Veya kendiniz inşa edebilirsiniz; o kadar zor değil.
Stephen Cleary

1
@Stephan: Tamam bunu deneyeceğim. Belki de bu asenkron veri-görüntüleme-model-senaryosu hakkında güzel bir makale hazırdır. Örneğin, bağlanmak {Binding PropName.Result}benim için önemsiz değil.
Gerard

101

Eşzamansız özellik desteği olmadığından, yalnızca eşzamansız yöntemler olduğundan eşzamansız olarak çağıramazsınız. Bunun gibi, iki seçenek, CTP içinde asenkron yöntemler gerçekten sadece bir yöntem döndürür olmasından hem yararlanarak vardır Task<T>ya Task:

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

Veya:

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}

1
Cevabınız için teşekkürler. Seçenek A: Bir Görevi Döndürmek, bağlayıcı amaç için gerçekten egzersiz yapmaz. Seçenek B: .Sonuç, belirttiğiniz gibi, UI iş parçacığını (Silverlight'ta) engeller, bu nedenle işlemin bir arka plan iş parçacığında yürütülmesini gerektirir. Bu fikirle uygulanabilir bir çözüm bulup bulamayacağımı göreceğim.
Doguhan Uluca

3
@duluca: private async void SetupList() { MyList = await MyAsyncMethod(); } Async işlemi tamamlanır tamamlanmaz, MyList'in ayarlanmasına (ve ardından INPC'yi uygularsa otomatik olarak bağlanmasına) neden olacak bir yöntem kullanmayı deneyebilirsiniz ...
Reed Copsey

Özellik bir sayfa kaynağı olarak ilan bir nesne olması gerekir, bu yüzden gerçekten bu çağrı alıcıdan kaynaklanan gerekli. Lütfen bulduğum çözüm için cevabıma bakın.
Doguhan Uluca

1
@duluca: Bu, etkili bir şekilde, yapmanızı önerdiğim şeydi ... Bununla birlikte, Başlığa birden çok kez hızlı bir şekilde erişirseniz, mevcut çözümünüz getTitle()simulatenously için birden fazla çağrıya yol açacaktır ...
Reed Copsey

Çok iyi bir nokta. Özel durumum için bir sorun olmasa da, isLoading için bir boole denetimi sorunu çözecektir.
Doguhan Uluca

55

Gerçekten ayrışmış mimarisi nedeniyle get yönteminden kaynaklanan çağrı gerekiyordu. Bu yüzden aşağıdaki uygulamayı buldum.

Kullanım: Başlık , bir ViewModel veya statik olarak sayfa kaynağı olarak bildirebileceğiniz bir nesnedir. Bağlayın ve getTitle () döndüğünde değer UI'yi engellemeden doldurulur.

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

9
updfrom'dan 18/07/2012 Win8 RP'de Dispatcher çağrısını şu şekilde değiştirmeliyiz: Window.Current.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, async () => {Title = GetTytleAsync (url);});
Anton Sizikov

7
@ChristopherStevenson, ben de öyle düşündüm, ama böyle olduğuna inanmıyorum. Alıcı bir yangın olarak yürütüldüğünden ve unuttuğundan, tamamlayıcıdan sonra ayarlayıcıyı çağırmadan, alıcı çıkarmayı bitirdiğinde bağlanma güncellenmeyecektir.
Iain

3
Hayır, bir yarış durumu vardı ve vardı, ancak kullanıcı 'RaisePropertyChanged ("Başlık")' nedeniyle görmez. Tamamlanmadan geri döner. Ancak, tamamlandıktan sonra özelliği ayarlıyorsunuz. PropertyChanged olayını tetikler. Ciltçi tekrar mülk değerini alır.
Medeni Baykal

1
Temel olarak, ilk alıcı boş bir değer döndürür, ardından güncellenir. GetTitle öğesinin her seferinde çağrılmasını istiyorsak, kötü bir döngü olabileceğini unutmayın.
tofutim

1
Ayrıca, bu zaman uyumsuz çağrıdaki istisnaların tamamen yutulacağının farkında olmalısınız. Uygulamanızda işlenmeyen istisna işleyicinize bile ulaşmazlar.
Philter

9

Ben sadece ilk null dönen değeri beklemek ve sonra gerçek değeri almak düşünüyorum, bu yüzden Pure MVVM (örneğin PCL projesi) aşağıdaki en zarif çözüm olduğunu düşünüyorum:

private IEnumerable myList;
public IEnumerable MyList
{
  get
    { 
      if(myList == null)
         InitializeMyList();
      return myList;
     }
  set
     {
        myList = value;
        NotifyPropertyChanged();
     }
}

private async void InitializeMyList()
{
   MyList = await AzureService.GetMyList();
}

3
Bu derleyici uyarıları oluşturmuyor muCS4014: Async method invocation without an await expression
Nick

6
Bu tavsiyeye uymakta çok şüpheci olun . Bu videoyu izleyin ve ardından kendi fikrinizi oluşturun: channel9.msdn.com/Series/Three-Essential-Tips-for-Async/… .
Contango

1
"Async void" yöntemlerini kullanmaktan kaçınmalısınız!
SuperJMN

1
her not akıllıca bir cevapla gelmeli, @SuperJMN bize nedenini açıklar mısın?
Juan Pablo Garcia Coello

1
@Contango İyi video. " async voidYalnızca üst düzey işleyiciler ve benzerleri için kullanın" diyor . Bunun "ve benzerleri" olarak nitelendirilebileceğini düşünüyorum.
HappyNomad

7

Bunun gibi kullanabilirsiniz Task:

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }

5

Ben düşündüm .GetAwaiter (). GetResult () tam olarak bu sorunun çözümü, değil mi? Örneğin:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            _Title = getTitle().GetAwaiter().GetResult();
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

5
Bu sadece engelleme ile aynıdır .Result- asenkron değildir ve kilitlenmelere neden olabilir.
McGuireV10

IsAsync = True eklemeniz gerekir
Alexsandr Ter

Cevabım hakkındaki geri bildirimi takdir ediyorum; Birisinin bu kilitlenmelerin nerede olduğunu görmek için bir örnek vermesini gerçekten çok isterdim, böylece eylemde görebilirsiniz
bc3tech

2

" Eşzamansız mülkünüz" bir görünüm modelinde olduğundan AsyncMVVM'yi kullanabilirsiniz :

class MyViewModel : AsyncBindableBase
{
    public string Title
    {
        get
        {
            return Property.Get(GetTitleAsync);
        }
    }

    private async Task<string> GetTitleAsync()
    {
        //...
    }
}

Senkronizasyon bağlamını ve özellik değişikliği bildirimini sizin yerinize halleder.


Bir mülk olmak, öyle olmak zorunda.
Dmitry Shechtman

Üzgünüm, ama belki de bu kodun noktasını kaçırdım. Lütfen biraz açıklayabilir misiniz?
Patrick Hofman

Özellikler tanım gereği engelleniyor. GetTitleAsync (), sözdizimsel şekeri bir "async alıcısı" olarak işlev görür.
Dmitry Shechtman

1
@DmitryShechtman: Hayır, engellemesine gerek yok. Değişiklik bildirimleri ve durum makineleri tam olarak budur. Ve tanım gereği engellemiyorlar. Tanımı gereği zaman uyumludurlar. Bu engelleme ile aynı şey değildir. "Engelleme", ağır işler yapabilecekleri ve yürütmek için önemli zaman harcayabilecekleri anlamına gelir. Bu da tam olarak hangi özelliklerin YAPMAMASI gerektiğidir.
quetzalcoatl

1

Necromancing.
.NET Core / NetStandard2'de aşağıdakiler Nito.AsyncEx.AsyncContext.Runyerine kullanabilirsiniz System.Windows.Threading.Dispatcher.InvokeAsync:

class AsyncPropertyTest
{

    private static async System.Threading.Tasks.Task<int> GetInt(string text)
    {
        await System.Threading.Tasks.Task.Delay(2000);
        System.Threading.Thread.Sleep(2000);
        return int.Parse(text);
    }


    public static int MyProperty
    {
        get
        {
            int x = 0;

            // /programming/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // /programming/41748335/net-dispatcher-for-net-core
            // https://github.com/StephenCleary/AsyncEx
            Nito.AsyncEx.AsyncContext.Run(async delegate ()
            {
                x = await GetInt("123");
            });

            return x;
        }
    }


    public static void Test()
    {
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
        System.Console.WriteLine(MyProperty);
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
    }


}

Basitçe seçerseniz System.Threading.Tasks.Task.Runveya System.Threading.Tasks.Task<int>.Runo zaman işe yaramaz.


-1

Aşağıdaki örneğimin @ Stephen-Cleary'nin yaklaşımını izleyebileceğini düşünüyorum ama kodlanmış bir örnek vermek istedim. Bu veri bağlama bağlamında kullanım içindir, örneğin Xamarin.

Sınıfın kurucusu - ya da aslında bağımlı olduğu başka bir mülkün ayarlayıcısı - bir beklemeye veya bloğa gerek kalmadan görevi tamamladıktan sonra mülkü dolduracak bir asenkron boşluk çağırabilir. Sonunda bir değer aldığında, kullanıcı arayüzünüzü NotifyPropertyChanged mekanizması aracılığıyla güncelleyecektir.

Bir kurucudan aysnc geçersizliğini çağırmanın herhangi bir yan etkisi konusunda emin değilim. Belki de bir yorumcu hata işleme vb.

class MainPageViewModel : INotifyPropertyChanged
{
    IEnumerable myList;

    public event PropertyChangedEventHandler PropertyChanged;

    public MainPageViewModel()
    {

        MyAsyncMethod()

    }

    public IEnumerable MyList
    {
        set
        {
            if (myList != value)
            {
                myList = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
                }
            }
        }
        get
        {
            return myList;
        }
    }

    async void MyAsyncMethod()
    {
        MyList = await DoSomethingAsync();
    }


}

-1

Bu sorunla karşılaştığımda, bir ayarlayıcıdan veya bir kurucudan bir eşzamansız yöntem eşzamanlılığını çalıştırmaya çalışmak beni UI iş parçacığında bir kilitlenmeye soktu ve bir olay işleyicisi kullanmak genel tasarımda çok fazla değişiklik gerektirdi.
Çözüm, sık sık, sadece açıkça başka bir iş parçacığı işlemi işlemek ve bitirmek için beklemek için ana iş parçacığı almak için dolaylı olmak istediklerini açıkça yazmak oldu:

string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();

Çerçeveyi kötüye kullandığımı iddia edebilirsiniz, ancak işe yarıyor.


-1

Tüm yanıtları gözden geçiriyorum ancak hepsinde bir performans sorunu var.

örneğin:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

Deployment.Current.Dispatcher.InvokeAsync (async () => {Title = getTitle ();}) bekliyor;

iyi bir cevap olmayan dağıtıcı kullanın.

ama basit bir çözüm var, sadece yap:

string _Title;
    public string Title
    {
        get
        {
            if (_Title == null)
            {   
                Task.Run(()=> 
                {
                    _Title = getTitle();
                    RaisePropertyChanged("Title");
                });        
                return;
            }
            return _Title;
        }
        set
        {
            if (value != _Title)
            {
                _Title = value;
                RaisePropertyChanged("Title");
            }
        }
    }

eğer işleviniz asyn ise getTitle () yerine getTitle () kullanın. wait ()
Mehdi Rastegari

-4

Proerty'i şu şekilde değiştirebilirsiniz: Task<IEnumerable>

ve şöyle bir şey yapın:

get
{
    Task<IEnumerable>.Run(async()=>{
       return await getMyList();
    });
}

ve MyList'i beklerken kullanın;

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.