getter 또는 setter에서 비동기 메소드를 호출하는 방법은 무엇입니까?


223

C #의 getter 또는 setter에서 비동기 메서드를 호출하는 가장 우아한 방법은 무엇입니까?

자신을 설명하는 데 도움이되는 의사 코드가 있습니다.

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

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

4
내 질문은 왜 그런가. 속성은 일반적으로 거의 또는 매우 빠른 작업을 수행해야한다는 점에서 필드와 같은 것을 모방해야합니다. 장기 실행 속성이 있으면 메소드로 작성하는 것이 훨씬 낫기 때문에 호출자가 더 복잡한 작업을 알 수 있습니다.
제임스 마이클 헤어

@James : 정확히 맞습니다. 이것이 CTP에서 명시 적으로 지원되지 않는 이유입니다. 즉, 항상 type의 속성을 만들 수 있습니다. 즉, Task<T>즉시 반환되고 정상적인 속성 의미가 있으며 필요에 따라 비동기식으로 처리 될 수 있습니다.
리드 경찰

17
@ 제임스 Mvvm과 Silverlight를 사용하는 것이 필요합니다. 데이터로드가 느리게 수행되는 속성에 바인딩 할 수 있기를 원합니다. 내가 사용하고있는 ComboBox 확장 클래스는 InitializeComponent () 단계에서 바인딩이 필요하지만 실제 데이터로드는 훨씬 나중에 발생합니다. 가능한 적은 코드로 달성하려고 할 때 getter와 async는 완벽한 조합처럼 느껴집니다.
Doguhan Uluca


제임스와 리드, 당신은 항상 최첨단이 있다는 것을 잊고있는 것 같습니다. WCF의 경우 속성에 배치되는 데이터가 올바른지 확인하고 암호화 / 암호 해독을 사용하여 확인해야했습니다. 해독에 사용하는 기능은 타사 공급 업체의 비동기 기능을 사용하기 위해 발생합니다. (여기서는 할 수 없습니다).
RashadRivera

답변:


211

C #에서 속성을 사용할 수 없는 기술적 이유 async는 없습니다. "비동기 특성"이 옥시 모론이기 때문에 의도적 인 설계 결정이었습니다.

속성은 현재 값을 반환해야합니다. 백그라운드 작업을 시작해서는 안됩니다.

일반적으로 누군가 "비동기 속성"을 원할 때 실제로 원하는 것은 다음 중 하나입니다.

  1. 값을 반환하는 비동기 메서드 이 경우 속성을 async메서드로 변경하십시오 .
  2. 데이터 바인딩에 사용할 수 있지만 비동기 적으로 계산 / 검색해야하는 값입니다. 이 경우 async포함 객체에 팩토리 메소드를 사용하거나 async InitAsync()메소드를 사용하십시오 . 데이터 바운드 값은 default(T)값을 계산 / 검색 할 때까지입니다.
  3. 작성하는 데 비용이 많이 들지만 나중에 사용하기 위해 캐시해야하는 값입니다. 이 경우 AsyncLazy 내 블로그 또는 AsyncEx 라이브러리에서 사용하십시오 . 이것은 당신에게 await유능한 재산을 줄 것 입니다.

업데이트 : 최근 "async OOP"블로그 게시물 중 하나에서 비동기 속성 을 다룹니다 .


포인트 2에서 imho는 속성을 설정 하면 기본 데이터 (생성자뿐만 아니라)를 다시 초기화 해야하는 일반적인 시나리오를 고려하지 않습니다 . 사용 이외의 다른 방법이 Nito AsyncEx를 또는 사용은 Dispatcher.CurrentDispatcher.Invoke(new Action(..)?
제라드

@ Gerard : 그 경우 포인트 (2)가 작동하지 않는 이유를 모르겠습니다. 구현 INotifyPropertyChanged한 다음 이전 값을 반환할지 아니면 default(T)비동기 업데이트가 진행 중인지 결정 하십시오.
Stephen Cleary

1
@ Stephan : 좋아, 그러나 setter에서 async 메서드를 호출하면 CS4014에 "대기하지 않음"경고가 표시됩니다 (또는 Framework 4.0에서만 해당됩니까?). 이 경우 경고를 억제하는 것이 좋습니다.
Gerard

@ Gerard : 내 첫 번째 권장 사항은 NotifyTaskCompletionfrom AsyncEx 프로젝트 를 사용하는 것 입니다. 또는 자신 만의 것을 만들 수도 있습니다. 그렇게 어렵지 않습니다.
Stephen Cleary

1
@Stephan : 알겠습니다. 이 비동기 데이터 바인딩 뷰 모델 시나리오에 대한 좋은 기사가있을 수 있습니다. 예를 들어 바인딩하는 {Binding PropName.Result}것은 사소한 것이 아닙니다.
Gerard

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 :. 결과는 언급했듯이 Silverlight에서 UI 스레드를 차단하므로 백그라운드 스레드에서 작업을 실행해야합니다. 이 아이디어로 실행 가능한 솔루션을 제안 할 수 있는지 살펴 보겠습니다.
Doguhan Uluca

3
@duluca : private async void SetupList() { MyList = await MyAsyncMethod(); } 비동기 작업이 완료 되 자마자 MyList를 설정 한 다음 (INPC를 구현하면 자동으로 바인딩 함) 다음과 같은 방법을 사용할 수도 있습니다.
Reed Copsey

이 속성은 페이지 리소스로 선언 한 객체에 있어야하므로이 호출은 게터에서 시작해야했습니다. 내가 찾은 솔루션에 대한 내 대답을 참조하십시오.
Doguhan Uluca

1
@duluca : 효과적으로, 내가 제안한 것은 ... 실현하기, 그러나 타이틀에 여러 번 빠르게 액세스하면 현재 솔루션이 여러 번의 호출로 이어질 것입니다 getTitle()...
Reed Copsey

아주 좋은 지적입니다. 내 경우에는 문제가 아니지만 isLoading에 대한 부울 검사로 문제가 해결됩니다.
Doguhan Uluca

55

필자는 분리 된 아키텍처로 인해 get 메소드에서 시작하는 호출이 필요했습니다. 그래서 다음 구현을 생각해 냈습니다.

사용법 : 제목 은 ViewModel 또는 페이지 리소스로 정적으로 선언 할 수있는 개체에 있습니다. 여기에 바인딩하면 getTitle ()이 반환 될 때 UI를 차단하지 않고 값이 채워집니다.

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 18/07/2012 우리가 디스패처 전화를 변경해야 Win8 RP에 : Window.Current.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, 비동기 () => {제목 = await를 GetTytleAsync (URL);});
Anton Sizikov

7
@ ChristopherStevenson, 나도 그렇게 생각했지만 그 경우는 믿지 않습니다. 게터가 화재로 실행되고 잊혀지기 때문에 완료시 세터를 호출하지 않으면 게터가 excuting을 마쳤을 때 바인딩이 업데이트되지 않습니다.
Iain

3
아니요, 경쟁 조건이 있지만 'RaisePropertyChanged ( "Title")'때문에 사용자에게 표시되지 않습니다. 완료되기 전에 반환됩니다. 그러나 완료 후 속성을 설정합니다. PropertyChanged 이벤트가 발생합니다. 바인더는 재산의 가치를 다시 얻습니다.
Medeni Baykal

1
기본적으로 첫 번째 getter는 null 값을 반환 한 다음 업데이트됩니다. 매번 getTitle을 호출하려면 루프가 잘못 될 수 있습니다.
tofutim

1
또한 해당 비동기 호출의 예외는 완전히 삼킨다는 점에 유의해야합니다. 응용 프로그램에서 처리되지 않은 예외 처리기 (있는 경우)에 도달하지도 않습니다.
Philter

9

첫 번째 null을 반환하고 실제 값을 얻는 값을 기다릴 수 있다고 생각하므로 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
"비동기 무효"방법을 사용하지 않아야합니다!
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

'비동기 속성'이 뷰 모델에 있으므로 AsyncMVVM을 .

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

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

동기화 컨텍스트와 속성 변경 알림을 처리합니다.


재산이기 때문에 그렇습니다.
Dmitry Shechtman

미안하지만 어쩌면이 코드의 요점을 놓쳤을 수도 있습니다. 좀 더 자세히 설명해 주시겠습니까?
Patrick Hofman

정의에 따라 속성이 차단되고 있습니다. GetTitleAsync ()는 구문 설탕을 "비동기 게터"로 사용합니다.
Dmitry Shechtman

1
@DmitryShechtman : 아니요, 차단할 필요는 없습니다. 이것이 바로 변경 알림 및 상태 시스템입니다. 그리고 그들은 정의에 의해 차단되지 않습니다. 그것들은 정의상 동 기적입니다. 이것은 차단과 동일하지 않습니다. "차단"은 무거운 작업을 수행하고 실행하는 데 상당한 시간이 걸릴 수 있음을 의미합니다. 이것은 바로 속성이하지 말아야 할 것입니다.
quetzalcoatl

1

괴롭힘.
.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과 같은 데이터 바인딩 컨텍스트에서 사용하기위한 것입니다.

클래스의 생성자 또는 클래스가 의존하는 다른 속성의 setter는 대기 또는 블록이 없어도 작업 완료시 속성을 채우는 비동기 void를 호출 할 수 있습니다. 마지막으로 값을 얻으면 NotifyPropertyChanged 메커니즘을 통해 UI를 업데이트합니다.

생성자에서 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 () => {제목 = await 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 () 대신 getTitle (). wait ()을 사용하십시오.
Mahdi Rastegari

-4

속성을 다음과 같이 변경할 수 있습니다 Task<IEnumerable>

다음과 같은 작업을 수행하십시오.

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

MyList를 기다리는 것처럼 사용하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.