在构造函数中调用异步方法
本文关键字:异步方法 调用 构造函数 | 更新日期: 2023-09-27 17:58:12
>总结:我想在构造函数中调用异步方法。这可能吗?
详细信息:我有一个名为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;
}
}
最好的解决方案是承认下载的异步性质并为其设计。
换句话说,确定应用程序在下载数据时的外观。让页面构造函数设置该视图,然后开始下载。下载完成后,更新页面以显示数据。
我有一篇关于异步构造函数的博客文章,您可能会发现它很有用。此外,一些 MSDN 文章;一个是关于异步数据绑定(如果使用 MVVM(,另一个是关于异步最佳实践(即,应避免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
}
}
基本上我们要做的是将构造函数设为私有,并制作我们自己的公共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();
在任何构造函数中执行一些耗时的操作的快速方法是创建一个操作并异步运行它们。
new Action( async() => await InitializeThingsAsync())();
运行这段代码既不会阻塞您的 UI,也不会给您留下任何松散的线程。如果您需要更新任何 UI(考虑到您没有使用 MVVM 方法(,您可以使用调度程序按照许多人的建议执行此操作。
注意:此选项仅在没有任何init
或onload
或navigated
重写的情况下提供从构造函数开始执行方法的方法。即使在施工完成后,这很可能会继续运行。因此,此方法调用的结果在构造函数本身中可能不可用。
我的首选方法:
// caution: fire and forget
Task.Run(async () => await someAsyncFunc());
尝试替换它:
myLongList.ItemsSource = writings;
有了这个
Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);
简单地说,指的是斯蒂芬·克利里 https://stackoverflow.com/a/23051370/267000
创建页面应在构造函数中创建任务,并且应将这些任务声明为类成员或将其放入任务池中。
您的数据是在这些任务期间获取的,但这些任务应该在代码中等待,即在某些 UI 操作上,即确定单击等。
我在WP中开发了这样的应用程序,我们在开始时创建了一大堆任务。
你可以试试AsyncMVVM。
第 2.xaml 页:
<PhoneApplicationPage x:Class="Page2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ListView ItemsSource="{Binding Writings}" />
</PhoneApplicationPage>
第 2.xaml 页.cs:
public partial class Page2
{
InitializeComponent();
DataContext = new ViewModel2();
}
视图模型2.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;
}
}
}
永远不要打电话.等待(( 或 .结果,因为这将锁定您的应用程序。 也不要启动新任务,只需调用 继续
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
为了在构造函数中使用异步并确保数据在实例化类时可用,可以使用以下简单模式:
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;
参加派对有点晚了,但我认为很多人都在为此苦苦挣扎......
我也一直在寻找这个。为了使您的方法/操作异步运行而无需等待或阻塞线程,您需要通过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
Brian Lagunas展示了一个我非常喜欢的解决方案。更多信息他的优酷视频
溶液:
添加任务扩展方法
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 应用,请将异步函数添加到页面的 Page_Loaded
方法。
如果您希望它等待任务完成,您可以像下面这样改进 madlars 代码。(我在.net核心3.1上尝试过它工作(
var taskVar = Task.Run(async (( => await someAsyncFunc(((;
taskVar.Wait((;
您可以将异步调用放在单独的方法中,并在构造函数中调用该方法。虽然,这可能会导致某些变量值在您预期的时间不可用的情况。
public NewTravelPageVM(){
GetVenues();
}
async void GetVenues(){
var locator = CrossGeolocator.Current;
var position = await locator.GetPositionAsync();
Venues = await Venue.GetVenues(position.Latitude, position.Longitude);
}