在构造函数中调用异步方法?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在构造函数中调用异步方法?相关的知识,希望对你有一定的参考价值。
简介:我想在构造函数中调用异步方法。这可能吗?
详细信息:我有一个名为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;
}
}
最好的解决方案是承认下载和设计的异步性质。
换句话说,在下载数据时确定应用程序的外观。让页面构造函数设置该视图,然后开始下载。下载完成后,更新页面以显示数据。
我在asynchronous constructors上有一篇博文,你会发现它很有用。还有一些MSDN文章;一个在asynchronous data-binding(如果你使用MVVM)和另一个在asynchronous best practices(即,你应该避免async void
)。
你也可以这样做:
Task.Run(() => this.FunctionAsync()).Wait();
我想分享一种我一直用来解决这些问题的模式。我觉得它运作得相当好。当然,它只有在你控制了什么调用构造函数时才有效。以下示例
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();
尝试替换这个:
myLongList.ItemsSource = writings;
有了这个
Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);
简单地说,指的是Stephen Cleary https://stackoverflow.com/a/23051370/267000
您的创建页面应该在构造函数中创建任务,您应该将这些任务声明为类成员或将其放在任务池中。
在这些任务期间获取您的数据,但是应该在代码中等待这些任务,即在一些UI操作上,即Ok Click等。
我在WP中开发了这样的应用程序,我们在启动时创建了一大堆任务。
你可以试试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();
}
ViewModel.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;
}
}
}
派对有点晚了,但我觉得很多人都在努力...
我一直在寻找这个。为了让你的方法/动作在不等待或阻塞线程的情况下运行异步,你需要通过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
以上是关于在构造函数中调用异步方法?的主要内容,如果未能解决你的问题,请参考以下文章