静的クラスでラッピングするコードを書いたときに、メソッドがマルチスレッドで同時に呼ばれたときの動き(コンフリクトが発生するかどうか)が気になって、確認してみました。
結論としては、
- メソッドに引数として渡された物は、値型・参照型の別に関わらず、メソッド呼び出しで上書きされることはない。
- プロパティにセットすることで渡された値型の項目は、上書きされてしまう。
- プロパティにセットすることで渡された参照型の項目への参照先情報は、上書きされてしまう(後からセットされた参照先への参照になる)。
というところです。
よく考えれば、静的クラスとして実体が一つであっても、マルチスレッドのそれぞれのスレッドでの関数呼び出しは個別に行われるわけで、引数としてスタックに積まれるのが、実体だろうと実体への参照だろうと、扱いに変わりはないのはあたりまえのことですね 🙂 プロパティも読み書きされる実体は関数内の項目なので、書き込みで情報が上書きされるのは当然です 🙂
あとは、ある項目(値型の項目、参照型の参照先の実体項目)が、他のところ(別スレッドで走る自メソッドを含む)で使われている場合ですが、これは、静的クラスであろうとなかろうと、マルチスレッドなプログラムで、複数のスレッドから読み書きされる項目は適切な排他制御を行うという原則に変わりないでしょう 😉
確認するために書いたコードと実行結果もついでに掲載しておきます。
プログラムはクラス(参照型)と構造体(値型)のインスタンスを引数とプロパティで引き渡すクラスと静的クラスを作成して、新規タスクを生成して併行呼び出しをしています。主タスクは1秒後にメソッドを呼び出すようにして、メソッドの呼び出し順序を保証しています。
呼び出されるメソッドは、途中で入力待ちになるので、ここで後から呼び出される主タスクと同期します(並行動作の確保)。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MultipleCallTest001
{
class Program
{
static void Main(string[] args)
{
const int sleepTime = 1000;
Console.WriteLine("== phase-1(static class) start! ==");
var task = Task.Factory.StartNew(() =>
{
var dataa = new dataA
{
Name = "Task"
};
sClassB.dataa = dataa;
var datab = new dataB
{
Name = "Task"
};
sClassB.datab = datab;
sClassB.DoProcess(dataa, datab);
Console.WriteLine("Task; result prp: {0}", dataa.ResultData1);
Console.WriteLine("Task; result prm: {0}", dataa.ResultData2);
});
Thread.Sleep(sleepTime);
var dataam = new dataA
{
Name = "Main"
};
sClassB.dataa = dataam;
var databm = new dataB
{
Name = "Main"
};
sClassB.datab = databm;
sClassB.DoProcess(dataam, databm);
Console.WriteLine("Task; result prp: {0}", dataam.ResultData1);
Console.WriteLine("Task; result prm: {0}", dataam.ResultData2);
Console.WriteLine("== phase-1 end. ==");
Console.WriteLine("== phase-2(class) start! ==");
var task2 = Task.Factory.StartNew(() =>
{
var dataa = new dataA
{
Name = "Task"
};
var datab = new dataB
{
Name = "Task"
};
var classa = new classA
{
dataa = dataa,
datab = datab
};
classa.DoProcess(dataa, datab);
Console.WriteLine("Task; result prp: {0}", dataa.ResultData1);
Console.WriteLine("Task; result prm: {0}", dataa.ResultData2);
});
Thread.Sleep(sleepTime);
var dataama = new dataA
{
Name = "Main"
};
var databmb = new dataB
{
Name = "Main"
};
var classb = new classA
{
dataa = dataama,
datab = databmb
};
classb.DoProcess(dataama, databmb);
Console.WriteLine("Task; result prp: {0}", dataama.ResultData1);
Console.WriteLine("Task; result prm: {0}", dataama.ResultData2);
Console.WriteLine("== phase-2 end ==");
Console.ReadLine();
}
}
public class dataA
{
public string Name { get; set; }
public string ResultData1 { get; set; }
public string ResultData2 { get; set; }
}
public struct dataB
{
public string Name { get; set; }
public string ResultData1 { get; set; }
public string ResultData2 { get; set; }
}
public class classA
{
public dataA dataa { get; set; }
public dataB datab { get; set; }
public void DoProcess(dataA parmDataa, dataB parmDatab)
{
Console.WriteLine("classA-data; Name: {0} (prp-class)", dataa.Name);
Console.WriteLine("classA-dataa; Name: {0} (prp-struct)", datab.Name);
Console.WriteLine("classA-data; Name: {0} (prm-class)", parmDataa.Name);
Console.WriteLine("classA-dataa; Name: {0} (prm-struct)", parmDatab.Name);
Console.WriteLine("classA wait!");
Console.ReadLine();
Console.WriteLine("classA-data; Name: {0} (prp-class)", dataa.Name);
Console.WriteLine("classA-dataa; Name: {0} (prp-struct)", datab.Name);
Console.WriteLine("classA-data; Name: {0} (prm-class)", parmDataa.Name);
Console.WriteLine("classA-dataa; Name: {0} (prm-struct)", parmDatab.Name);
dataa.ResultData1 = DateTime.Now.ToString();
parmDataa.ResultData2 = DateTime.Now.ToString();
parmDatab.ResultData2 = DateTime.Now.ToString();
}
}
public static class sClassB
{
public static dataA dataa { get; set; }
public static dataB datab { get; set; }
static sClassB()
{
}
public static void DoProcess(dataA parmDataa, dataB parmDatab)
{
Console.WriteLine("sClassB-data; Name: {0} (prp-class)", dataa.Name);
Console.WriteLine("sClassB-datab; Name: {0} (prp-struct)", datab.Name);
Console.WriteLine("sClassB-data; Name: {0} (prm-class)", parmDataa.Name);
Console.WriteLine("sClassB-datab; Name: {0} (prm-struct)", parmDatab.Name);
Console.WriteLine("sClassB wait!");
Console.ReadLine();
Console.WriteLine("sClassB-data; Name: {0} (prp-class)", dataa.Name);
Console.WriteLine("sClassB-dataa; Name: {0} (prp-struct)", datab.Name);
Console.WriteLine("sClassB-data; Name: {0} (prm-class)", parmDataa.Name);
Console.WriteLine("sClassB-datab; Name: {0} (prm-struct)", parmDatab.Name);
dataa.ResultData1 = DateTime.Now.ToString();
parmDataa.ResultData2 = DateTime.Now.ToString();
parmDatab.ResultData2 = DateTime.Now.ToString();
}
}
}
実行結果
最初の赤枠で囲ったところが、静的クラスで動かした、先行タスクの表示内容です。プロパティが保持する内容とパラメータが保持する内容が異なっています(主タスクのプロパティと同じ物になっている)。また、セットしたはずの時間が表示されません。セットしたのが書き換えられた参照先なので、元の参照先の項目を表示させてもセットされていません。