在构造函数中调用异步方法?

时间:2014-04-13 20:49:04

标签: c# windows-phone-8 constructor visual-studio-2013 async-await

摘要:我想在构造函数中调用异步方法。这可能吗?

详细信息:我有一个名为getwritings()的方法来解析JSON数据。如果我只是在getwritings()方法中调用async并将await放在其左侧,那么一切正常。但是,当我在页面中创建LongListView并尝试填充它时,我发现getWritings()令人惊讶地返回null并且LongListView为空。

要解决此问题,我尝试将getWritings()的返回类型更改为Task<List<Writing>>,然后通过getWritings().Result在构造函数中检索结果。但是,这样做会最终阻止UI线程。

public partial class Page2 : PhoneApplicationPage
{
    List<Writing> writings;

    public Page2()
    {
        InitializeComponent();
        getWritings();
    }

    private async void getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}

13 个答案:

答案 0 :(得分:94)

最好的解决方案是确认下载和设计的异步性。

换句话说,确定在下载数据时应用程序应该是什么样子。让页面构造器设置那个视图,然后开始下载。当下载完成更新页面以显示数据时。

我在asynchronous constructors上有一篇你可能觉得有用的博客文章。还有一些MSDN文章;一个在asynchronous data-binding上(如果您正在使用MVVM)而另一个在asynchronous best practices上(即,您应该避免使用async void)。

答案 1 :(得分:63)

您也可以这样做:

Task.Run(() => this.FunctionAsync()).Wait();

答案 2 :(得分:45)

我想分享我一直用来解决这些问题的模式。我觉得它运作得相当好。当然,它只有在你控制了什么调用构造函数时才有效。以下示例

public class MyClass
{
    public static async Task<MyClass> Create()
    {
        var myClass = new MyClass();
        await myClass.Initialize();
        return myClass;
    }

    private MyClass()
    {

    }

    private async Task Initialize()
    {
        await Task.Delay(1000); // Do whatever asynchronous work you need to do
    }
}

基本上我们所做的是将构造函数设为私有,并创建我们自己的公共静态异步方法,该方法负责创建MyClass的实例。通过使构造函数私有并将静态方法保持在同一个类中,我们确保没有人可以“无意中”创建此类的实例而无需调用正确的初始化方法。围绕对象创建的所有逻辑仍然包含在类中(仅在静态方法中)。

var myClass1 = new MyClass() // Cannot be done, the constructor is private
var myClass2 = MyClass.Create() // Returns a Task that promises an instance of MyClass once it's finished
var myClass3 = await MyClass.Create() // asynchronously creates and initializes an instance of MyClass

在当前场景中实现它看起来像:

public partial class Page2 : PhoneApplicationPage
{
    public static async Task<Page2> Create()
    {
        var page = new Page2();
        await page.getWritings();
        return page;
    }

    List<Writing> writings;

    private Page2()
    {
        InitializeComponent();
    }

    private async Task getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}

而不是做

var page = new Page2();

你会做的

var page = await Page2.Create();

答案 3 :(得分:4)

尝试替换它:

myLongList.ItemsSource = writings;

用这个

Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);

答案 4 :(得分:2)

简单地说,请参阅Stephen Cleary https://stackoverflow.com/a/23051370/267000

您的创建页面应该在构造函数中创建任务,您应该将这些任务声明为类成员或将其放在任务池中。

您的数据是在这些任务期间获取的,但是这些任务应该在代码中等待,即在某些UI操作上,即Ok Click等。

我在WP中开发了这样的应用程序,我们在开始时创建了一大堆任务。

答案 5 :(得分:2)

在任何构造函数中执行一些耗时的操作的快速方法是创建一个动作并异步运行它们。

new Action( async() => await GetDataAsync())();

运行这段代码既不会阻塞您的UI,也不会留下任何松散的线程。而且,如果您需要更新任何UI(考虑到您未使用MVVM方法),则可以使用Dispatcher来执行许多建议。

A注意:仅当您没有任何initonloadnavigated覆盖时,此选项才提供一种从构造方法开始执行方法的方法。即使在施工完成后,它很可能仍将继续运行。因此,此方法调用的结果可能在构造函数本身中可用。

答案 6 :(得分:1)

您可以尝试AsyncMVVM

Page2.xaml:

<PhoneApplicationPage x:Class="Page2"
                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <ListView ItemsSource="{Binding Writings}" />
</PhoneApplicationPage>

Page2.xaml.cs:

public partial class Page2
{
    InitializeComponent();
    DataContext = new ViewModel2();
}

ViewModel2.cs:

public class ViewModel2: AsyncBindableBase
{
    public IEnumerable<Writing> Writings
    {
        get { return Property.Get(GetWritingsAsync); }
    }

    private async Task<IEnumerable<Writing>> GetWritingsAsync()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");
            yield return writing;
        }
    }
}

答案 7 :(得分:0)

我的首选方法:

// caution: fire and forget
Task.Run(async () => await someAsyncFunc());

答案 8 :(得分:0)

永远不要调用.Wait()或.Result,因为这将锁定您的应用程序。 也不要启动新任务,只需调用ContinueWith

public class myClass
{
  public myClass
  {
    GetMessageAsync.ContinueWith(GetResultAsync);
  }

  async Task<string> GetMessageAsync()
  {
    return await Service.GetMessageFromAPI(); 
  }
private async Task GetResultAsync(Task<string> resultTask)
{
    if (resultTask.IsFaulted)
{
      Log(resultTask.Exception); 
}
eles
{
 //do what ever you need from the result
}
}
}

https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern

答案 9 :(得分:0)

为了在构造函数中使用异步并确保在实例化类时数据可用,可以使用以下简单模式:

class FooClass : IFooAsync
{        
    FooClass 
    {
        this.FooAsync = InitFooTask();
    }

    public Task FooAsync { get; }

    private async Task InitFooTask()
    {
        await Task.Delay(5000);
    }
}

界面:

public interface IFooAsync
{
    Task FooAsync { get; }
}

用法:

FooClass foo = new FooClass();    
if (foo is IFooAsync)
    await foo.FooAsync;

答案 10 :(得分:0)

Brian Lagunas展示了我非常喜欢的解决方案。更多信息his youtube video

解决方案:

添加TaskExtensions方法

  public static class TaskExtensions
{
    public static async void Await(this Task task, Action completedCallback = null ,Action<Exception> errorCallBack = null )
    {
        try
        {
            await task;
            completedCallback?.Invoke();
        }
        catch (Exception e)
        {
            errorCallBack?.Invoke(e);
        }
    }
}

用法:

  public class MyClass
{

    public MyClass()
    {
        DoSomething().Await();
       // DoSomething().Await(Completed, HandleError);
    }

    async Task DoSomething()
    {
        await Task.Delay(3000);
        //Some works here
        //throw new Exception("Thrown in task");
    }

    private void Completed()
    {
        //some thing;
    }

    private void HandleError(Exception ex)
    {
        //handle error
    }

}

答案 11 :(得分:-1)

聚会晚了一点,但我认为许多人为此感到挣扎...

我也一直在寻找这个。为了使您的方法/操作异步运行而不等待或阻塞线程,您需要通过SynchronizationContext将其排队,因此我想出了以下解决方案:

我为此做了一个助手类。

public static class ASyncHelper
{

    public static void RunAsync(Func<Task> func)
    {
        var context = SynchronizationContext.Current;

        // you don't want to run it on a threadpool. So if it is null, 
        // you're not on a UI thread.
        if (context == null)
            throw new NotSupportedException(
                "The current thread doesn't have a SynchronizationContext");

        // post an Action as async and await the function in it.
        context.Post(new SendOrPostCallback(async state => await func()), null);
    }

    public static void RunAsync<T>(Func<T, Task> func, T argument)
    {
        var context = SynchronizationContext.Current;

        // you don't want to run it on a threadpool. So if it is null, 
        // you're not on a UI thread.
        if (context == null)
            throw new NotSupportedException(
                "The current thread doesn't have a SynchronizationContext");

        // post an Action as async and await the function in it.
        context.Post(new SendOrPostCallback(async state => await func((T)state)), argument);
    }
}

用法/示例:

public partial class Form1 : Form
{

    private async Task Initialize()
    {
        // replace code here...
        await Task.Delay(1000);
    }

    private async Task Run(string myString)
    {

        // replace code here...
        await Task.Delay(1000);
    }

    public Form1()
    {
        InitializeComponent();

        // you don't have to await nothing.. (the thread must be running)
        ASyncHelper.RunAsync(Initialize);
        ASyncHelper.RunAsync(Run, "test");

        // In your case
        ASyncHelper.RunAsync(getWritings);
    }
}

这适用于Windows.Forms和WPF

答案 12 :(得分:-2)

您可以将异步调用放在单独的方法中,然后在构造函数中调用该方法。 虽然,这可能会导致某些变量值在您期望时不可用的情况。

 public NewTravelPageVM(){
   GetVenues();              
 }

 async void  GetVenues(){
   var locator = CrossGeolocator.Current;
   var position = await locator.GetPositionAsync();
   Venues = await Venue.GetVenues(position.Latitude, position.Longitude);
 }