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

Kaa*_*rak 158 c# constructor async-await windows-phone-8 visual-studio-2013

简介:我想在构造函数中调用异步方法.这可能吗?

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

为了解决这个问题,我尝试更改getWritings()to 的返回类型,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 111

最好的解决方案是承认下载和设计的异步性质.

换句话说,在下载数据时确定应用程序的外观.让页面构造函数设置视图,然后开始下载.下载完成后,更新页面以显示数据.

我有一篇关于异步构造函数的博客文章,你可能会发现它很有用.还有一些MSDN文章; 一个用于异步数据绑定(如果您使用的是MVVM),另一个用于异步最佳实践(即,您应该避免async void).

  • 太糟糕了,这个答案特别适用于UI代码.我正在编写一个Windows服务,需要构造函数将一些东西加载到内存中,数据来自其他地方的一些异步方法. (11认同)
  • @Ellesedil:这个答案特指MVVM,因为这个问题是专门询问MVVM的.我的[博客文章](http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html)适用于Win32服务,如果您愿意,可以提出自己的问题. (5认同)
  • @BVernon:这是一个较旧的,我没有提到,因为它不好。:) `ContinueWith` 是一种[危险的低级方法](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html)(链接到我的博客)。现代代码应该使用“await”来代替。旁注:在 JS 中,`then` 也应该替换为 `await`。 (2认同)

Pet*_*nar 71

你也可以这样做:

Task.Run(() => this.FunctionAsync()).Wait();
Run Code Online (Sandbox Code Playgroud)

  • 注意,此解决方案同步阻止线程. (40认同)
  • 与`FunctionAsync().Wait()`有何不同? (14认同)
  • 您应该非常小心,因为它不仅会阻塞线程,还会导致死锁(请参阅此处:http://stackoverflow.com/questions/15021304/an-async-await-example-that-c​​auses-a-僵局).特别容易在winforms中陷入僵局 (9认同)
  • @IlyaChernomordik当它阻塞运行构造函数的线程时,`this.FunctionAsync()`调用在另一个线程中执行,并在完成后恢复构造函数.这种模式实际上应该可以工作,而直接的`this.FunctionAsync().Wait()`确实会死锁.问题:不依赖于`FunctionAsync`中的特定线程关联,因为它的执行线程可以是任何东西. (3认同)
  • 非常适合测试设置 (2认同)
  • 我不知道该如何感谢您,我遇到了非常糟糕的问题,并且通过您的出色解决方案解决了它 (2认同)

Sha*_*azi 51

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上我们所做的是将构造函数设为私有,并创建我们自己的公共静态异步方法,该方法负责创建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
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

而不是做

var page = new Page2();
Run Code Online (Sandbox Code Playgroud)

你会做的

var page = await Page2.Create();
Run Code Online (Sandbox Code Playgroud)

  • 事实并非如此。它提供了问题的替代解决方案 (3认同)
  • 这种方法使用[工厂模式](http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html)。请参阅另一个广受欢迎的类似答案 [此处](http://stackoverflow.com/a/12520574/1497596)。 (2认同)
  • 如何将其与依赖注入(构造函数注入)一起使用? (2认同)
  • 通过静态创建传递依赖项 (2认同)
  • 无论我盯着这个看多久,我就是不明白。这如何让您从构造函数调用异步代码? (2认同)

mdl*_*ars 9

我的首选方法:

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

  • 你有一定道理。尽管如此,您至少应该更加突出地表明您正在启动一项“即发即忘”的任务。现在看来,首选的方法是将其分配给丢弃:`_ = Task.Run(async...` (3认同)
  • 这并不能确保当您想要使用数据时数据可用,因为创建它们的“任务”被触发并被遗忘。如果出现异常,数据将永远不可用。 (2认同)
  • @TheodorZoulias - 正确,但是问题如下:“我想在构造函数中调用异步方法。这可能吗?” 可以执行,但了解其中的陷阱也是有意义的。无论如何,我在该线程的任何其他示例中都没有看到这个确切的实现。此外,根据情况,“即发即弃”可能是一种有效的方法,并且我认为“数据永远不会可用”可以通过异常处理来解决。我主要看到这种模式与 MVVM 和桌面/移动设备一起使用,您可以使用双向数据绑定来刷新。 (2认同)

Anu*_*rma 8

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

new Action( async() => await InitializeThingsAsync())();
Run Code Online (Sandbox Code Playgroud)

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

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


csh*_*aml 5

尝试替换这个:

myLongList.ItemsSource = writings;
Run Code Online (Sandbox Code Playgroud)

有了这个

Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);
Run Code Online (Sandbox Code Playgroud)