在C#中线程之间如何传递数据

21

虽然我在Stack Overflow上找到了一些关于我的问题的问题,但是我仍然无法自行解决,因此我将尝试在这里询问。我会粘贴代码,所以我认为解释起来会更容易。

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中更改它是否可能?感谢回答:)
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);
    }

}  

}
我正在制作游戏机器人。我希望它能在不同的线程中读取游戏内存(更改内存列表),然后使用其他几种方法(在不同的线程中)从该列表中读取并根据内存值执行游戏操作。它似乎可以正常工作,但如果您认为它可能存在安全隐患,我希望能够使其更加安全。
希望我没有在这里贴错东西。

7
这是一本书:C# 多线程编程指南, Joseph Albahari - Sjoerd
4
好的,谢谢!多亏了那本书,现在问题已经解决了 :) - Patryk
1
@Patryk:如果您不通过添加或删除项目(即更改其长度)来修改列表,则仅交换int值或其他引用值是原子性的,并且本身不会导致线程问题(实际上,在.NET中,涉及32位或更小值的任何赋值都是原子性的)。但是游戏循环通常在单个线程中完成(读取输入->计算新位置->检查碰撞->渲染)。为什么您要坚持使用多个线程?您需要一个线程来执行循环,并使用主(GUI)线程进行渲染。除此之外,您考虑过XNA吗? - vgru
1
哦,现在我明白了。我以为你在制作游戏。那么你如何获取游戏内存数据,是读取不同进程拥有的内存吗?你已经处理过这部分了吗?有多种方法可以做到这一点。根据你的逻辑复杂性,你发布的内容相当简单,可能已经足够了。但我仍然相信,你的线程可以在单次迭代中唤醒、获取内存、处理它并返回睡眠状态。我稍后可以发布另一个带有一些模式的答案,现在我有点忙。 - vgru
好的,我期待您的回答! :P @进程内存 - 是的,我已经处理了那部分并且工作非常好 :P(感谢关心!) :) - Patryk
显示剩余4条评论
2个回答

36

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

通常,生产者/消费者模式更适合使用Queue(先进先出的集合类型),而不是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<T> 的集合,它是一个线程安全的 FIFO 队列,在访问时无需加锁,从而简化了代码:

ConcurrentQueue

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
        }
    }
}

如果你只希望消费者线程定期以块状获取项目,则可以将其更改为:

具有阈值的ConcurrentQueue(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标志,表示是时候停止了,然后设置信号以取消暂停消费者并允许其结束。我留这个作为你的练习。


我只使用少量线程从一个对象中进行读取,如果我想要修改,我计划允许一个线程修改一个对象。那么没有你们的东西可以吗? - Patryk
2
如果一个不是线程安全的对象正在被另一个线程写入,那么你需要锁定读和写。就性能而言,如果有许多线程在读取,只有一个在写入,你可以考虑使用ReaderWriterLockSlim,它被设计为允许许多并发读取器具有更高的吞吐量。但对于少数读者来说,它可能仍然比常规锁慢,并且稍微复杂一些(所以我现在不会考虑它)。 - vgru
1
所以,如果你想从不同的线程修改对象,则需要一个线程安全的对象。如果它是列表,你最有可能需要一个ConcurrentQueue。如果不是,请发布一些附加详细信息。 - vgru
我将使用简单结构的列表、一些字符串和几个整数,就这样:P 虽然读取和写入都不会有问题。感谢您的回复!如果您能使用我的代码向我展示ConcurrentQueue的使用示例,我将不胜感激 :P - Patryk
@Patryk:我现在有点忙,会尽量在几个小时内更新。你可以继续添加一些问题的细节,因为我不完全理解这些线程将如何交互。请描述生产者线程实际正在执行的操作,以及为什么需要多个消费者。 - vgru
1
好吧,我无法发布对我的问题的答案(我在这里太新了 :P),而且我也不想通过“添加评论”将代码粘贴在这里,所以我编辑了我的主要问题。有更多信息(希望如此):P。 - Patryk

0

在这种情况下使用

label.Invoke(..., textForLabel)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接