【问题标题】:C# lock to simultaneously read/write and display resultsC#锁同时读/写和显示结果
【发布时间】:2017-01-12 00:15:52
【问题描述】:

这是我的问题:

假设我有这个程序(我会尽量简化): receiveResultThread 等待来自不同网络客户端的结果,而 displayResultToUIThread 使用收到的所有结果更新 UI。

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的静态属性)?

您认为这是编写此类程序的好方法还是您能提出更好的模式?

感谢您的帮助!

【问题讨论】:

标签: c# multithreading


【解决方案1】:

公共(不是在谈论public static)锁定对象往往是危险的。请看here

锁定公共对象是不好的做法的原因是您永远无法确定谁 ELSE 锁定了该对象。

此外,只有 List&lt;T&gt; 并从外部范围添加对象也可能是一种气味。

在我看来,在Tests 中有一个方法AddTest 会更好

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 或类似的名称,该类的用户都知道,它在某种程度上是线程安全的。

【讨论】:

  • 不应该是:lock(testLock) return executedTests.ToArray(); ?
【解决方案2】:

虽然您可以使用 Tasks 和 async/await 关键字来减少冗长,但我认为它不能完全解决您的问题。

我会假设 ExecutedTests 是一个您希望线程安全的列表(或类似),这就是您在访问它时创建锁的原因。

我会让列表本身成为线程安全的,而不是针对它的操作。这将消除对锁或锁对象的需求。

您可以自己实现它或使用 System.Collections.Concurrent 命名空间中的某些东西。

附言

如果线程要在进程退出时关闭(中止),则应将线程的 IsBackground 属性设置为 true。

【讨论】:

  • 感谢您的回复。我想我会走这条路。而且,是的,在真正的程序中,所有线程都在后台。这只是为了缩短代码。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多