วิธีการเรียกใช้วิธีการ async จาก getter หรือ setter


223

อะไรจะเป็นวิธีที่สวยงามที่สุดในการโทรหาวิธีการ async จาก getter หรือ setter ใน C #

นี่คือรหัสเทียมเพื่อช่วยอธิบายตัวเอง

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

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

4
คำถามของฉันจะเป็นสาเหตุ คุณสมบัติควรจะเลียนแบบบางอย่างเช่นเขตข้อมูลซึ่งโดยทั่วไปแล้วควรทำงานเล็ก ๆ น้อย ๆ (หรืออย่างน้อยก็เร็ว) หากคุณมีคุณสมบัติที่ใช้เวลานานมันจะดีกว่าที่จะเขียนมันเป็นวิธีการเพื่อให้ผู้โทรรู้ว่ามันเป็นงานที่ซับซ้อนมากขึ้น
James Michael Hare

@James: ถูกต้อง - และฉันสงสัยว่าทำไม CTP ไม่ได้รับการสนับสนุนอย่างชัดเจน ที่ถูกกล่าวว่าคุณสามารถทำให้ทรัพย์สินประเภทTask<T>ซึ่งจะกลับมาทันทีมีความหมายของคุณสมบัติปกติและยังคงให้สิ่งที่จะได้รับการปฏิบัติแบบอะซิงโครนัสได้ตามต้องการ
Reed Copsey

17
@ James ความต้องการของฉันเกิดขึ้นจากการใช้ Mvvm และ Silverlight ฉันต้องการเชื่อมโยงกับคุณสมบัติที่การโหลดข้อมูลทำได้อย่างเกียจคร้าน คลาสส่วนขยาย ComboBox ที่ฉันใช้ต้องมีการเชื่อมโยงเพื่อให้เกิดขึ้นในระยะ InitializeComponent () อย่างไรก็ตามการโหลดข้อมูลจริงจะเกิดขึ้นในภายหลัง ในการพยายามทำให้สำเร็จด้วยโค้ดน้อยที่สุดผู้ทะเยอทะยานและ async จะรู้สึกเหมือนเป็นส่วนผสมที่ลงตัว
Doguhan Uluca

เกี่ยวข้อง: stackoverflow.com/a/33942013/11635
Ruben Bartelink

ยากอบและรีดดูเหมือนว่าคุณจะลืมไปว่ามีคดีขอบอยู่เสมอ ในกรณีของ WCF ฉันต้องการตรวจสอบว่าข้อมูลที่วางอยู่ในสถานที่นั้นถูกต้องและจะต้องมีการตรวจสอบโดยใช้การเข้ารหัส / ถอดรหัส ฟังก์ชั่นที่ฉันใช้สำหรับการถอดรหัสเกิดขึ้นเพื่อใช้งานฟังก์ชั่น async จากผู้ผลิตรายอื่น (ฉันไม่สามารถทำอะไรได้มากที่นี่)
RashadRivera

คำตอบ:


211

ไม่มีเหตุผลทางเทคนิคที่asyncไม่อนุญาตให้ใช้คุณสมบัติใน C # มันเป็นการตัดสินใจออกแบบที่มีจุดมุ่งหมายเพราะ "คุณสมบัติแบบอะซิงโครนัส" เป็นรูปแบบของโอมาซิรอน

คุณสมบัติควรส่งคืนค่าปัจจุบัน พวกเขาไม่ควรเริ่มต้นปฏิบัติการพื้นหลัง

โดยปกติเมื่อมีคนต้องการ "คุณสมบัติแบบอะซิงโครนัส" สิ่งที่พวกเขาต้องการคือหนึ่งในสิ่งเหล่านี้:

  1. วิธีการแบบอะซิงโครนัสที่ส่งคืนค่า ในกรณีนี้ให้เปลี่ยนคุณสมบัติเป็นasyncวิธีการ
  2. ค่าที่สามารถใช้ในการผูกข้อมูล แต่ต้องคำนวณ / ดึงข้อมูลแบบอะซิงโครนัส ในกรณีนี้ใช้asyncวิธีการจากโรงงานสำหรับวัตถุที่มีหรือใช้async InitAsync()วิธีการ ค่าที่ผูกข้อมูลจะเป็นdefault(T)จนกว่าจะมีการคำนวณหรือดึงค่า
  3. ค่าที่มีราคาแพงในการสร้าง แต่ควรเก็บไว้สำหรับใช้ในอนาคต ในกรณีนี้ให้ใช้AsyncLazy จากบล็อกของฉันหรือห้องสมุด AsyncEx สิ่งนี้จะทำให้คุณมีawaitคุณสมบัติที่สามารถ

อัปเดต:ฉันครอบคลุมคุณสมบัติอะซิงโครนัสในโพสต์บล็อก "async OOP" ล่าสุดของฉัน


ในจุดที่ 2 คุณไม่ต้องคำนึงถึงสถานการณ์ปกติที่การตั้งค่าคุณสมบัติควรเริ่มต้นข้อมูลพื้นฐานอีกครั้ง (ไม่เฉพาะในตัวสร้าง) มีวิธีอื่นนอกเหนือจากการใช้Nito AsyncExหรือใช้Dispatcher.CurrentDispatcher.Invoke(new Action(..)?
เจอราร์ด

@ Gerard: ฉันไม่เห็นว่าทำไมจุด (2) จะไม่ทำงานในกรณีนั้น เพียงใช้งานINotifyPropertyChangedและตัดสินใจว่าคุณต้องการคืนค่าเดิมหรือdefault(T)ในขณะที่การอัพเดทแบบอะซิงโครนัสกำลังดำเนินการอยู่
Stephen Cleary

1
@ สเตฟาน: โอเค แต่เมื่อฉันเรียกเมธอด async ใน setter ฉันจะได้รับคำเตือน "ไม่รอคอย" CS4014 (หรือนั่นคือเฉพาะใน Framework 4.0 หรือไม่) คุณแนะนำให้ระงับคำเตือนนั้นในกรณีเช่นนี้หรือไม่?
เจอราร์ด

@Gerard: คำแนะนำแรกของฉันจะใช้จากโครงการNotifyTaskCompletion AsyncEx ของฉัน หรือคุณสามารถสร้างของคุณเอง มันไม่ยากเลย
Stephen Cleary

1
@ สเตฟาน: โอเคจะลองดู อาจเป็นบทความที่ดีเกี่ยวกับสถานการณ์จำลอง databinding-viewmodel-asynchronous นี้ เช่นการผูกมัดกับ{Binding PropName.Result}ไม่ใช่เรื่องสำคัญสำหรับฉันที่จะหา
เจอราร์ด

101

คุณไม่สามารถเรียกมันแบบอะซิงโครนัสได้เนื่องจากไม่มีการสนับสนุนคุณสมบัติแบบอะซิงโครนัสวิธีการแบบอะซิงโครนัสเท่านั้น ดังนั้นจึงมีสองตัวเลือกทั้งการใช้ประโยชน์จากความจริงที่ว่าวิธีการแบบอะซิงโครนัสใน CTP เป็นเพียงวิธีการที่ส่งกลับTask<T>หรือTask:

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

หรือ:

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

1
ขอขอบคุณสำหรับการตอบสนองของคุณ. ตัวเลือก A: การส่งคืนภารกิจไม่ได้ออกกำลังกายเพื่อการผูกมัดจริงๆ ตัวเลือก B: .Result ตามที่คุณพูดถึงบล็อกเธรด UI (ใน Silverlight) ดังนั้นต้องการให้การดำเนินการดำเนินการบนเธรดพื้นหลัง ฉันจะดูว่าฉันสามารถหาวิธีแก้ปัญหาที่ใช้การได้กับแนวคิดนี้หรือไม่
Doguhan Uluca

3
@duluca: คุณสามารถลองใช้วิธีที่เป็นเช่นprivate async void SetupList() { MyList = await MyAsyncMethod(); } นี้ได้ซึ่งจะทำให้ MyList ถูกตั้งค่า (จากนั้นผูกโดยอัตโนมัติหากใช้ INPC) ในทันทีที่การดำเนินการ async เสร็จสมบูรณ์ ...
Reed Copsey

สถานที่ให้บริการจะต้องอยู่ในวัตถุที่ฉันประกาศเป็นทรัพยากรหน้าดังนั้นฉันต้องการการโทรนี้มาจาก getter จริงๆ โปรดดูคำตอบของฉันสำหรับวิธีแก้ปัญหาที่ฉันพบ
Doguhan Uluca

1
@duluca: นั่นคือการได้อย่างมีประสิทธิภาพ, สิ่งที่ผมบอกคุณ ... ตระหนักถึงแม้ว่านี้ถ้าคุณเข้าถึงชื่อหลายครั้งอย่างรวดเร็ว, การแก้ปัญหาในปัจจุบันของคุณจะนำไปสู่หลายสายเพื่อgetTitle()simulatenously ...
Reed Copsey

จุดที่ดีมาก แม้ว่าจะไม่ใช่ปัญหาสำหรับกรณีเฉพาะของฉันการตรวจสอบบูลีนสำหรับ isLoading จะแก้ไขปัญหาได้
Doguhan Uluca

55

ฉันต้องการการโทรเพื่อเริ่มต้นด้วยวิธีการรับเนื่องจากสถาปัตยกรรมที่แยกออกมาของฉัน ดังนั้นฉันจึงเกิดการดำเนินการดังต่อไปนี้

การใช้งาน: ชื่อเรื่องอยู่ใน ViewModel หรือวัตถุที่คุณสามารถประกาศแบบคงที่เป็นทรัพยากรของหน้าเว็บ ผูกเข้ากับมันและค่าจะได้รับการเติมโดยไม่มีการปิดกั้น UI เมื่อ getTitle () กลับมา

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
อัปเดตจาก 18/07/2012ใน Win8 RP เราควรเปลี่ยนการโทรไปที่: Window.Current.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, async () => {Title = รอ GetTytleAsync (url);});
Anton Sizikov

7
@ChristopherStevenson ฉันก็คิดเช่นกัน แต่ฉันไม่เชื่อว่าเป็นเช่นนั้น เนื่องจากทะเยอทะยานถูกดำเนินการเป็นไฟและลืมโดยไม่ต้องเรียกตัวตั้งค่าเมื่อเสร็จสิ้นการผูกจะไม่ได้รับการปรับปรุงเมื่อทะเยอทะยานเสร็จสิ้นการ excuting
เลน

3
ไม่มันมีและมีสภาพการแข่งขัน แต่ผู้ใช้จะไม่เห็นเพราะ 'RaisePropertyChanged ("ชื่อ")' มันกลับมาก่อนที่มันจะเสร็จสมบูรณ์ แต่หลังจากเสร็จสิ้นคุณกำลังตั้งค่าคุณสมบัติ เหตุการณ์นั้นเกิดเพลิงไหม้ PropertyChanged Binder ได้รับมูลค่าของทรัพย์สินอีกครั้ง
Medeni Baykal

1
โดยทั่วไปแล้วผู้ทะเลาะแรกจะคืนค่าเป็นโมฆะจากนั้นจะได้รับการอัพเดท โปรดทราบว่าถ้าเราต้องการให้ getTitle ถูกเรียกในแต่ละครั้งอาจมีลูปที่ไม่ดี
tofutim

1
คุณควรทราบด้วยว่าข้อยกเว้นใด ๆ ในการโทรแบบ async นั้นจะถูกกลืนหายไปอย่างสมบูรณ์ พวกเขาไม่สามารถเข้าถึงตัวจัดการข้อยกเว้นที่ไม่ได้จัดการของคุณในแอปพลิเคชันหากคุณมี
Philter

9

ฉันคิดว่าเราสามารถรอค่าที่เพิ่งกลับมาเป็นโมฆะก่อนจากนั้นรับค่าที่แท้จริงดังนั้นในกรณีของ Pure MVVM (ตัวอย่างโครงการ PCL) ฉันคิดว่าสิ่งต่อไปนี้เป็นโซลูชันที่หรูหราที่สุด:

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
สิ่งนี้ไม่สร้างคำเตือนคอมไพเลอร์CS4014: Async method invocation without an await expression
Nick

6
จะมากเชื่อตามคำแนะนำนี้ ดูวิดีโอนี้แล้วทำให้ใจของคุณเองขึ้น: channel9.msdn.com/Series/Three-Essential-Tips-for-Async/...
Contango

1
คุณควรหลีกเลี่ยงการใช้วิธี "async void"!
SuperJMN

1
ทุกตะโกนควรมาพร้อมคำตอบที่ชาญฉลาดคุณจะ @SuperJMN อธิบายว่าทำไม
Juan Pablo Garcia Coello

1
@Contango วิดีโอที่ดี เขากล่าวว่า "ใช้async voidสำหรับตัวจัดการระดับบนเท่านั้นและสิ่งที่ชอบ" ฉันคิดว่านี่อาจมีคุณสมบัติเป็น "และชอบ"
HappyNomad

7

คุณสามารถใช้Taskสิ่งนี้:

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

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

5

ฉันคิดว่า. GetAwaiter () GetResult () เป็นวิธีแก้ปัญหานี้ใช่ไหม? เช่น:

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

5
นั่นเป็นเหมือนกับการบล็อกด้วย.Result- มันไม่ได้เป็นแบบอะซิงก์และมันสามารถทำให้เกิดการหยุดชะงักได้
McGuireV10

คุณต้องเพิ่ม IsAsync = True
Alexsandr Ter

ฉันขอขอบคุณข้อเสนอแนะในคำตอบของฉัน; ฉันรักใครสักคนเพื่อเป็นตัวอย่างในการหยุดชะงักนี้ดังนั้นฉันสามารถเห็นมันในการดำเนินการ
bc3tech

2

เนื่องจาก "คุณสมบัติ async" ของคุณอยู่ใน viewmodel คุณสามารถใช้AsyncMVVM :

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

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

มันจะดูแลบริบทการประสานและการแจ้งเตือนการเปลี่ยนแปลงคุณสมบัติสำหรับคุณ


เป็นคุณสมบัติก็จะต้องมี
Dmitry Shechtman

ขออภัยฉันอาจพลาดจุดของรหัสนี้ไปแล้ว กรุณาอธิบายเพิ่มเติมหน่อยได้ไหม?
Patrick Hofman

คุณสมบัติถูกบล็อกโดยคำจำกัดความ GetTitleAsync () ทำหน้าที่เป็น "async getter" ทำให้น้ำตาลมีประโยค
Dmitry Shechtman

1
@DmitryShechtman: ไม่ไม่ต้องทำการบล็อก นี่คือสิ่งที่แจ้งเตือนการเปลี่ยนแปลงและเครื่องรัฐสำหรับ และพวกเขาไม่ได้ปิดกั้นตามคำจำกัดความ ซิงโครนัสตามคำนิยาม สิ่งนี้ไม่เหมือนกับการปิดกั้น "การบล็อก" หมายถึงพวกเขาอาจทำงานหนักและอาจใช้เวลานานในการดำเนินการ สิ่งนี้ในที่สุดก็เป็นสิ่งที่คุณสมบัติไม่ควรทำ
quetzalcoatl

1

Necromancing
ใน. NET Core / NetStandard2 คุณสามารถใช้Nito.AsyncEx.AsyncContext.Runแทน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"));
    }


}

หากคุณเพียงแค่เลือกSystem.Threading.Tasks.Task.RunหรือSystem.Threading.Tasks.Task<int>.Runแล้วมันจะไม่ทำงาน


-1

ฉันคิดว่าตัวอย่างของฉันด้านล่างอาจเป็นไปตามแนวทางของ @ Stephen-Cleary แต่ฉันต้องการยกตัวอย่างรหัส สิ่งนี้มีไว้สำหรับใช้ในบริบทการเชื่อมโยงข้อมูลเช่น Xamarin

คอนสตรัคเตอร์ของคลาส - หรือตัวตั้งค่าของคุณสมบัติอื่นที่ขึ้นอยู่กับมัน - อาจเรียกว่าโมฆะ async ที่จะเติมคุณสมบัติเมื่อเสร็จสิ้นงานโดยไม่ต้องรอหรือบล็อก เมื่อได้รับค่าในที่สุดก็จะอัปเดต UI ของคุณผ่านกลไก NotifyPropertyChanged

ฉันไม่แน่ใจเกี่ยวกับผลข้างเคียงของการเรียก aysnc void จากนวกรรมิก บางทีผู้วิจารณ์อาจอธิบายรายละเอียดเกี่ยวกับการจัดการข้อผิดพลาดและอื่น ๆ

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

เมื่อฉันพบปัญหานี้พยายามเรียกใช้วิธีการซิงค์แบบซิงโครไนซ์จาก setter หรือตัวสร้างทำให้ฉันเข้าสู่การหยุดชะงักในเธรด UI และการใช้ตัวจัดการเหตุการณ์จำเป็นต้องมีการเปลี่ยนแปลงในการออกแบบทั่วไปมากเกินไป
วิธีแก้ปัญหาคือบ่อยครั้งเพียงเขียนสิ่งที่ฉันต้องการให้เกิดขึ้นโดยปริยายซึ่งมีเธรดอื่นจัดการการดำเนินการและเพื่อให้เธรดหลักรอให้เสร็จ:

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

คุณสามารถยืนยันว่าฉันใช้เฟรมเวิร์กผิดปกติ แต่ใช้งานได้


-1

ฉันตรวจสอบคำตอบทั้งหมด แต่ทั้งหมดมีปัญหาด้านประสิทธิภาพ

ตัวอย่างเช่นใน:

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

ใช้โปรแกรมเลือกจ่ายงานที่ไม่ใช่คำตอบที่ดี

แต่มีวิธีแก้ปัญหาง่ายๆเพียงทำ:

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

ถ้าฟังก์ชั่นของคุณใช้ asyn ใช้ getTitle (). wait () แทน getTitle ()
Mahdi Rastegari

-4

คุณสามารถเปลี่ยนลูกน้องเป็น Task<IEnumerable>

และทำสิ่งที่ชอบ:

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

และใช้มันเหมือนรอ MyList;

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.