【问题标题】:Passing data between threads in c#c#线程间数据传递
【发布时间】:2012-02-18 10:33:30
【问题描述】:

我发现了一些关于我的问题的问题,但我仍然无法自己解决这个问题,所以我会尝试在这里提问。我把代码贴上来,这样我觉得会更容易解释。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Thread thread = new Thread(new ThreadStart(StartCalculation));
        thread.Start();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }


    public void StartCalculation()
    {
        List<int> numbers = new List<int>();
        for (int i = 0; i <= 100; i++)
        {
            numbers.Add(i);
            string textForLabel = i.ToString();
            label.SafeInvoke(d => d.Text = textForLabel);
        }

    }  
}
  • 我想从不同线程中启动的 StartCalculation 方法获得访问权限。我想从 Form1 访问该 int 列表(10 秒后有 10 个元素,20 秒后有 20 个元素,依此类推)。这可能吗?
  • 是否可以在 Form1() 中创建列表,然后在 StartCalculation 中更改它? 感谢您的回答:)

为 Groo 编辑- / -

public partial class Form1 : Form
{

List<int> list = new List<int>(); // list of int values from game's memory

public Form1()
{
    InitializeComponent();
    Thread thread = new Thread(new ThreadStart(refreshMemory));
    thread.Start();
    Thread thread2 = new Thread(new ThreadStart(checkMemory));
    thread2.Start();
}

private void Form1_Load(object sender, EventArgs e)
{
}

public void refreshMemory()
{        
    while (true)
    {
     // ... refresh game's memory and then, refresh list //
    Thread.Sleep(100);
    }
}  

public void checkMemory()
{

    while (true)
    {
     // eg. if (list[0] == 5) {game:: move_right()}// 
    Thread.Sleep(100);
    }

}  

}

我正在制作游戏机器人。我希望它在不同的线程(更改内存列表)中读取游戏的内存,然后使用其他一些方法(在不同的线程中)我想从该列表中读取并根据内存值执行游戏操作。它有效(或似乎有效),但如果你说它可能不安全,我想让它安全。

希望我没有把它贴在这里弄傻了。

【问题讨论】:

  • 好的,谢谢!感谢那本书,现在解决了:)
  • “10 秒后 10 个元素,20 秒后 20 个元素” - 为什么会出现这些延迟?如果后台线程以更快的速度生成元素怎么办?或者这应该作为生产者/消费者队列工作,但阈值是最小值。 10 件?
  • @Patryk:如果您没有通过添加或删除项目(即更改其长度)来修改列表,那么简单地交换一个 int 值或不同的参考值是原子完成的,并且本身不会导致线程问题(事实上,任何涉及 32 位或更小的值的赋值在 .NET 中都是原子的)。但是游戏循环通常在单个线程中进行(读取输入 -> 计算新位置 -> 检查碰撞 -> 渲染)。你为什么坚持多线程?您需要一个线程用于循环,并使用主(gui)线程进行渲染。除此之外,您是否考虑过 XNA?
  • 哦,现在我明白了。我以为你在做游戏。以及如何获取游戏内存数据,您是否正在读取不同进程拥有的内存?你已经处理过这部分了吗?有多种方法可以做到这一点。您发布的内容相当简单,取决于您的逻辑的复杂性,它可能就足够了。但我仍然相信您的线程可以在一次迭代中唤醒、获取内存、处理它并重新进入睡眠状态。稍后我可以发布另一个带有一些模式的答案,现在我有点忙。

标签: c# multithreading


【解决方案1】:

您需要某种形式的同步机制来在多个线程之间修改对象。如果您不使用专门的线程安全集合(这些在 .NET 4 中可用),则需要使用监视器进行锁定。

通常,更适合生产者/消费者模式的集合类型是Queue(FIFO 集合),而不是List

带有显式锁定的普通队列

private readonly object _lock = new object();
private readonly Queue<Item> _queue = new Queue<Item>();
private readonly AutoResetEvent _signal = new AutoResetEvent();

void ProducerThread()
{
    while (ShouldRun) 
    { 
        Item item = GetNextItem();

        // you need to make sure only
        // one thread can access the list
        // at a time
        lock (_lock)
        {
            _queue.Enqueue(item);
        }

        // notify the waiting thread
        _signal.Set();
    }

}

而在消费者线程中,您需要获取项目并对其进行处理:

void ConsumerThread()
{
    while (ShouldRun)
    {
        // wait to be notified
        _signal.Wait();

        Item item = null;

        do
        { 
           item = null;

           // fetch the item,
           // but only lock shortly
           lock (_lock)
           {
               if (_queue.Count > 0)
                  item = _queue.Dequeue(item);
           }

           if (item != null)
           {
              // do stuff
           }            
        }
        while (item != null); // loop until there are items to collect
    }
}

从 .NET 4 开始,有一个 ConcurrentQueue&lt;T&gt; 集合,一个线程安全的 FIFO,在访问它时无需锁定并简化代码:

并发队列

private readonly ConcurrentQueue<Item> _queue = new ConcurrentQueue<Item>();

void ProducerThread()
{
    while (ShouldRun) 
    { 
        Item item = GetNextItem();
        _queue.Enqueue(item);
        _signal.Set();
    }

}

void ConsumerThread()
{
    while (ShouldRun)
    {
        _signal.Wait();

        Item item = null;
        while (_queue.TryDequeue(out item))
        {
           // do stuff
        }
    }
}

最后,如果您只希望您的消费者线程定期以块的形式获取项目,您可以将其更改为:

具有阈值的并发队列(10 秒或 10 个项目)

private readonly ConcurrentQueue<Item> _queue = new ConcurrentQueue<Item>();

void ProducerThread()
{
    while (ShouldRun) 
    { 
        Item item = GetNextItem();
        _queue.Enqueue(item);

        // more than 10 items? panic!
        // notify consumer immediately

        if (_queue.Count >= 10)
           _signal.Set();
    }

}

void ConsumerThread()
{
    while (ShouldRun)
    {
        // wait for a signal, OR until
        // 10 seconds elapses
        _signal.Wait(TimeSpan.FromSeconds(10));

        Item item = null;
        while (_queue.TryDequeue(out item))
        {
           // do stuff
        }
    }
}

这种模式非常有用,可以将其抽象为一个泛型类,该类将生产和消费委托给外部代码。使其通用化将是一个很好的练习。

您还需要一个Stop 方法,该方法可能会设置一个volatile bool 标志,指示该停止,然后设置信号以取消暂停消费者并允许它结束。我会把这个留给你作为练习。

【讨论】:

  • 我只从一个对象读取几个线程,如果我想修改,我打算允许 1 个线程修改 1 个对象。那么没有你的东西可以吗?
  • 如果另一个线程正在写入一个不是线程安全的对象,那么您需要同时锁定读取和写入。性能方面,如果您有多个线程读取而只有一个线程写入,您可以考虑使用ReaderWriterLockSlim,它旨在为许多并发读取器提供更高的吞吐量。但是对于少数读者来说,它可能仍然比普通锁要慢,并且实现起来稍微复杂一些(所以我现在不会考虑)。
  • 所以,是的,如果你想从不同的线程修改它,你需要一个线程安全的对象。如果是列表,您很可能需要ConcurrentQueue。如果不是,请发布一些其他详细信息。
  • 不使用简单结构的列表,一些字符串和一些整数,就是这样:P 虽然很少阅读和 1 写作不会有问题。感谢您的回复!我会很感激向我展示一个使用我的代码使用 ConcurrentQueue 的示例:P
  • 好吧,我无法发布我的问题的答案(我在这里太新了:P),我不想通过“添加评论”将代码粘贴到这里,所以我已经编辑了我的主要问题。还有更多信息(我希望如此):P.
【解决方案2】:

在这种情况下使用

label.Invoke(..., textForLabel)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 1970-01-01
    • 2014-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多