まとめ:コンストラクタの中で非同期メソッドを呼びたいのですが。これは可能ですか?
Details:私はJSONデータを解析するgetwritings()
というメソッドを持っています。 async
メソッドでgetwritings()
を呼び出し、その左側にawait
を配置するだけですべてがうまくいきます。しかし、私が自分のページでLongListView
を作成してそれを移植しようとすると、getWritings()
が驚くべきことにnull
を返し、LongListView
が空であることがわかりました。
この問題に対処するために、戻り値の型getWritings()
をTask<List<Writing>>
に変更してから、getWritings().Result
を介してコンストラクターで結果を取得しました。しかし、それをするとIスレッドをブロックしてしまいます
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;
}
}
最善の解決策は、ダウンロードの非同期性を認識し、それを設計することです。
つまり、データのダウンロード中にアプリケーションがどのように見えるかを決めます。ページコンストラクタにthat viewを設定させ、ダウンロードを開始してください。ダウンロードが完了したらpdateデータを表示するページです。
私は 非同期コンストラクタ についてのブログ記事を持っています。また、いくつかのMSDNの記事。 1つは 非同期データバインディング (MVVMを使用している場合)、もう1つは 非同期ベストプラクティス (つまり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のインスタンスの作成を担当する独自のpublic static asyncメソッドを作成します。コンストラクタを非公開にし、静的メソッドを同じクラス内に保持することで、適切な初期化メソッドを呼び出さずにこのクラスのインスタンスを "誤って"作成することができるようになりました。オブジェクトの作成に関するすべてのロジックは、クラス内(静的メソッド内)にまだ含まれています。
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/2670
作成ページでコンストラクタ内にタスクを作成し、それらのタスクをクラスメンバーとして宣言するか、タスクプールに入れる必要があります。
あなたのデータはこれらのタスクの間にフェッチされます、しかしこれらのタスクはコード、すなわちいくつかのUI操作、すなわちOkクリックなどで待っているべきです。
私は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();
}
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;
}
}
}
任意のコンストラクタで時間のかかる操作を実行する簡単な方法は、アクションを作成して非同期で実行することです。
new Action( async () => await getWritings())();
このコードを実行しても、UIがブロックされることも、ゆるいスレッドが残ることもありません。また、UIを更新する必要がある場合(MVVMアプローチを使用していないと考えられる場合)、多くの人が示唆しているようにDispatcherを使用して更新できます。