C#锁定同时读/写并显示结果

时间:2017-01-11 15:21:37

标签: c# multithreading

这是我的问题:

说我有这个程序(我会尝试尽可能地简化): 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的静态属性)?

您认为这是编写此类程序的好方法,还是可以建议更好的模式?

感谢您的帮助!

2 个答案:

答案 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。