ある値を取得するプロパティがあって、その値は別メソッドが実行されることにより非同期に変更される場合、読み込みと書き込みの間で同期をとるようにしたうえで、書き込み動作が終了するまで待機状態が発生することになることから、非同期メソッドのように取り扱いたいことがあります(二分探索木から最小値を取得するプロパティなど。。。取得する機能をプロパティではなくて非同期メソッドで実装すればいいという話もありますが)。
つまり、UI スレッドをブロックしないように、次のように書きたいわけです。
a = await classA.PropertyAsync;
ただし、await はプロパティには書けません。なので、ちょっと工夫してプロパティからは Task<T> を返してあげることで、利用する側で上記のように書けるようになります。
ということでプログラムです。例示のプログラムはコンソールアプリケーションになっています。構造を例示することが目的なので、値の取得・設定は単純に int なフィールドの変数への読み書きにしています。
最初はプロパティを持つクラスから。
class HasValue
{
private System.Threading.ReaderWriterLockSlim _readWriteLock =
new System.Threading.ReaderWriterLockSlim();
private int _prop1;
public Task<int> Prop1Async
{
get
{
var task = Task.Factory.StartNew<int>(() =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Enter HasValue.Prop1:task");
_readWriteLock.EnterReadLock();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Enter HasValue.Prop1:ReadLock");
try
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} execute HasValue.Prop1:read");
return _prop1;
}
finally
{
_readWriteLock.ExitReadLock();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Exit HasValue.Prop1:ReadLock");
}
});
return task;
}
}
public async Task SetNumAsync(int num)
{
await Task.Factory.StartNew(() =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} enter HasValue.SetNum:task");
_readWriteLock.EnterWriteLock();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Enter HasValue.SetNum:WriteLock");
try
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Enter HasValue.SetNum:sleep");
System.Threading.Thread.Sleep(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} execute HasValue.SetNum:write({num})");
_prop1 = num;
}
finally
{
_readWriteLock.ExitWriteLock();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} Exit HasValue.SetNum:WriteLock");
}
});
}
}
書き込みは Task SetNumAsync(int num) 非同期メソッドで、読み出しは Task<int> Prop1Async プロパティで行っています。
読み出しと書き込みの同期制御には ReaderWriterLockSlim を使っています。
コンソール出力では、C# 6.0 で追加された文字列の整形用の構文(++c++; さんの説明へのリンク)を使っています。
Prop1Async プロパティは、プロパティの返り値の型が Task<int> になるようにしているのと、同期制御の構文を使っているのが特徴です。Task を返すことで、呼び出し元スレッドがブロックされることを回避し、別スレッドでの実行が完了した時点で完了が通知されます。
次に、このプロパティを呼び出す側です。Program.Main メソッドでは await が書けないので、利用するクラスを書きます。
class Sample
{
private HasValue hasValue = new HasValue();
public async Task GetInt()
{
System.Threading.Thread.Sleep(500);
var result = await hasValue.Prop1Async;
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} return task complete SampleProp.Prop1.val: {result}");
}
public async Task SetInt(int num)
{
await hasValue.SetNumAsync(num);
}
}
数値セットの Task SetInt(int num) 非同期メソッドとプロパティの読み出しを行っている Task GetInt() 非同期メソッドだけです。
GetInt メソッド中の Sleep メソッドは、実行させたときの時間調整をさせています。
最後に Program.Main メソッドです。
class Program
{
static void Main(string[] args)
{
var sample = new Sample();
sample.SetInt(50).Wait();
var readTask1 = sample.GetInt();
var writeTask = sample.SetInt(100);
var readTask2 = sample.GetInt();
Task.WaitAll(readTask1,readTask2, writeTask);
Console.WriteLine("Press any key...");
Console.ReadKey();
}
}
で、これを実行させて、コンソールに出力された表示内容は、次のとおりです。