【问题标题】:How can I make a value passed to a function that runs on a separate thread be changed on the original thread?如何在原始线程上更改传递给在单独线程上运行的函数的值?
【发布时间】:2010-02-11 23:39:07
【问题描述】:

我创建了一个抽象类,它实现了一个在单独的线程上运行另一个抽象方法的方法,如下所示:

 public abstract class ATest
    {
        Thread t;
        protected String status;
        public void Start()
        {
            ThreadStart ts = new ThreadStart(PerformTest);
            t = new Thread(ts);
            t.Start();

        }

        public ATest(String status)
        {
            this.status=status;
        }
        public abstract void PerformTest();
    }

这个想法是从 ATest 派生的类只实现 PerformTest 方法。因此,任何客户端都可以调用 Start() 方法在其自己的线程上启动 PerformTest() 中的操作。从 ATest 派生的类可能如下所示:

class ConcreteTest:ATest
    {
        public ConcreteTest(String status):base(status)
        {

        }

        public override void PerformTest()
        {
            // Do some things...
            // And some more...
            status = "changed";
        }

    }

创建 ConcreteTest 对象时,我想传入一些对象(在示例中为字符串)。当 PerformTest() 在其单独的线程上运行时,该对象的状态将根据 PerformTest() 中的操作结果进行更改。使用上述类的示例:

class Program
{
    static void Main(string[] args)
    {
        String isPassed = "original";
        ATest test = new ConcreteTest(isPassed);
        test.Start();
        Console.WriteLine(isPassed); // Prints out "original"
    }
}

所以我将 isPassed 设置为“原始”并将其传递给 ConcreteTest,后者在单独的线程上将值更改为“已更改”。因此,当我打印出 isPassed 时,我希望将值设置为“已更改”,但事实证明并非如此。我想这与在最初创建它的另一个线程上更改的值有关。谁能向我解释为什么我会出现这种行为,也许我可以做些什么来实现我正在寻找的功能(即在单独的线程上更改 isPassed 以便当线程完成时,控制台会打印出来“变了”?

【问题讨论】:

  • 你不是将ispassed的副本传递给抽象类吗???为什么你期望它改变???

标签: c# .net multithreading


【解决方案1】:

字符串是不可变的。

当您编写 status = "changed" 时,您不会更改任何现有的字符串对象。
相反,您正在更改 status 字段以引用完全不同的 String 实例。

status 字段与 Main 方法中的 isPassed 变量无关。当您编写new ConcreteTest(isPassed) 时,您将isPassed 变量的 传递给构造函数。
该参数与您传递给它的变量无关,除了现在,它们碰巧引用同一个对象。您的 status 字段也是如此。

最简单的方法是定义一个持有者类型,像这样:

class Holder<T> { public T Value { get; set; } }

如果需要,可以添加隐式强制转换和构造函数。

您也可以通过在基类中公开Status 属性并在Main 中写入test.Status 来做到这一点。


您还有一个不太明显的问题。
您的代码中没有任何内容会强制它等待其他线程完成,因此您的 Console.WriteLine 可能会在其他线程分配字段之前运行。

【讨论】:

  • “唯一的方法”?在编程中总是有几十种方法可以做任何事情。 :-)
  • 我认为 String 被当作一个对象,因此是通过引用而不是值传递的。显然,我错了。您的解决方案解决了我的问题。谢谢!
  • 我想到了一件事。这在 Holder 是泛型时有效,但如果 a 不将其定义为泛型,则它不起作用。这是为什么呢?
【解决方案2】:

通常您希望将结果存储在ConcreteTest 中,当任务完成后从ConcreteTest 中读取值,不要尝试将其推回原始位置。如果你真的想把它推回去,你可以像这样使用回调和 lambda。

class ConcreteTest:ATest
{
    public ConcreteTest(Action<string> statusResponder):base(statusResponder)
    {

    }

    public override void PerformTest()
    {
        // Do some things...
        // And some more...
        statusResponder("changed");
    }

}

class Program
{
    static void Main(string[] args)
    {
        String isPassed = "original";
        ATest test = new ConcreteTest(status => isPassed = status);
        test.Start();
        // Need to add a way to wait for task to be done here
        Console.WriteLine(isPassed); // Prints out "original"
    }
}

【讨论】:

  • 重新评论我现在已删除的答案:谢谢,我错过了问题的这方面。
  • @T.J. Crowder,np,是一个很好的答案,只是针对不同的问题。 :-)
  • 我尝试了这个解决方案,但是我得到了完全相同的结果。 :(
  • @Clean,您是否添加了代码以等待测试完成,注释指示的位置?
  • 是的,我做到了。我输入了一个 Thread.Sleep(2000) 等待 2 秒,这应该足以让测试完成。
猜你喜欢
  • 2018-09-20
  • 2012-11-20
  • 2018-07-24
  • 1970-01-01
  • 2015-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多