这是我的问题:
说我有这个程序(我会尝试尽可能地简化): receiveResultThread 等待来自不同网络客户端的结果,而 displayResultToUIThread 会更新收到所有结果的用户界面。
class Program
{
private static Tests TestHolder;
static void Main(string[] args)
{
TestHolder = new Tests();
Thread receiveResultsThread = new Thread(ReceiveResult);
receiveResultsThread.Start();
Thread displayResultToUIThread = new Thread(DisplayResults);
displayResultToUIThread.Start();
Console.ReadKey();
}
public static void ReceiveResult()
{
while (true)
{
if (IsNewTestResultReceivedFromNetwork())
{
lock (Tests.testLock)
TestHolder.ExecutedTests.Add(new Test { Result = "OK" });
}
Thread.Sleep(200);
}
}
private static void DisplayResults(object obj)
{
while (true)
{
lock (Tests.testLock)
{
DisplayAllResultInUIGrid(TestHolder.ExecutedTests);
}
Thread.Sleep(200);
}
}
}
class Test
{
public string Result { get; set; }
}
class Tests
{
public static readonly object testLock = new object();
public List<Test> ExecutedTests;
public Tests()
{
ExecutedTests = new List<Test>();
}
}
class UIManager
{
public static void DisplayAllResultInUIGrid(List<Test> list)
{
//Code to update UI.
}
}
考虑到范围是在另一个线程将测试添加到列表时不更新UI,使用它是安全的:
lock (Tests.testLock)
或者我应该使用:
lock (TestHolder.testLock)
(更改testLock的静态属性)?
您认为这是编写此类程序的好方法,还是可以建议更好的模式?
感谢您的帮助!
答案 0 :(得分:1)
公开(不是在谈论public static
)锁定对象往往是危险的。请参阅here
锁定公共对象的不良做法是,您永远无法确定ELSE是谁锁定该对象。
此外,只有List<T>
并从外部范围添加对象也可能是一种气味。
在我看来,在AddTest
Tests
会更好一点
class Tests
{
private static readonly object testLock = new object();
private List<Test> executedTests;
public Tests()
{
ExecutedTests = new List<Test>();
}
public void AddTest(Test t)
{
lock(testLock)
{
executedTests.Add(t);
}
}
public IEnumerable<Test> GetTests()
{
lock(testLock)
{
return executedTests.ToArray();
}
}
[...]
}
测试类的客户端不必担心正确使用锁定对象。确切地说,他们不必担心你班上的任何内部人员。
无论如何,您可以将您的班级重命名为ConcurrentTestsCollection
或类似名称,该类用户知道它在某种程度上是线程安全的。
答案 1 :(得分:0)
虽然您可以使用Tasks和async / await关键字来减少冗长,但我认为它不能完全解决您的问题。
我将假设ExecutedTests是一个你希望线程安全的List(或类似),这就是你在访问它时创建一个锁的原因。
我会使列表本身是线程安全的,而不是针对它的操作。这将消除对锁或锁对象的需要。
您可以自己实现,也可以在System.Collections.Concurrent命名空间中使用。
P.S。
如果要在退出进程时关闭(中止)线程,则应将Thread的IsBackground属性设置为true。