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

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

细节:我有一个名为getwritings()的方法,用于解析JSON数据。如果我在async方法中调用getwritings()并将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;
}
}
221681 次浏览

试着替换一下:

myLongList.ItemsSource = writings;

用这个

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

最好的解决方案是承认下载的异步性质,并为此进行设计。

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

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

你可以试试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;
}
}
}

你也可以这样做:

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
}
}

基本上,我们所做的是将构造函数设为私有,并将自己的static async方法设为公共方法,该方法负责创建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();

简单地说,引用Stephen Cleary https://stackoverflow.com/a/23051370/267000

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

你的数据在这些任务期间被获取,但这些任务应该在代码中等待,即在一些UI操作上,即Ok单击等。

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

有点晚了,但我认为很多人都在努力解决这个问题……

我也一直在找这个。为了让你的方法/动作运行异步而不等待或阻塞线程,你需要通过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。表单和WPF

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

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

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

注意:此选项仅在你没有任何initonloadnavigated重写的情况下,为你提供了从构造函数开始执行方法的方法。最有可能的是,即使在建设完成后,它仍将继续运行。因此,此方法调用的结果可能在构造函数本身中可用。

我喜欢的方法是:

// caution: fire and forget
Task.Run(async () => await someAsyncFunc());
不要调用. wait()或. result,因为这将锁定你的应用程序。 也不要启动一个新的Task,只需调用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://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern

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

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;
你可以把异步调用放在一个单独的方法中,然后在构造函数中调用该方法。 不过,这可能会导致某些变量值在您期望它们的时候不可用的情况
 public NewTravelPageVM(){
GetVenues();
}


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

布莱恩·拉古纳斯展示了一个我非常喜欢的解决方案。更多信息他的YouTube视频

解决方案:

添加一个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
}


}

答案很简单,如果你正在开发一个UWP应用程序,然后将async函数添加到页面的Page_Loaded方法中。

如果你想让它等待任务完成,你可以像下面这样改进madlars代码。(我尝试了。net core 3.1,它工作)

var taskVar =任务。Run(async () =>等待someAsyncFunc ());

taskVar.Wait ();