从线程返回值

本文关键字:返回值 线程 | 更新日期: 2023-09-27 18:36:59

我对编程很陌生,希望你能帮助我。我的任务是制作 3 个不同的线程来读取具有给定 int 的 txt 文件。然后,它必须打印出这些值的总和。我想从我制作的三个线程中访问 int。我该怎么做?

这是我的一些代码:

class Program
{
    static void Main()
    {
        Thread t1 = new Thread(ReadFile1);
        Thread t2 = new Thread(ReadFile2);
        Thread t3 = new Thread(ReadFile3);
        t1.Start();
        t2.Start();
        t3.Start();
        System.Console.WriteLine("Sum: ");
        Console.WriteLine();                                                                                        
        Console.WriteLine("");
        System.Console.ReadKey();                                                                                                 
    }
    public static void ReadFile1()
    {
        System.IO.StreamReader file1 = new System.IO.StreamReader({FILEDESTINATION});        
        int x = int.Parse(file1.ReadLine());
    }

从线程返回值

.NET 中的任务系统使这变得非常容易。在几乎所有情况下,您都应该更喜欢它而不是原始线程。对于您的示例:

var t1 = Task.Run(() => ReadFile(path1));
var t2 = Task.Run(() => ReadFile(path2));
var t3 = Task.Run(() => ReadFile(path3));
Console.WriteLine("Sum: {0}", t1.Result + t2.Result + t3.Result);
static int ReadFile(string path) {
    using(var file = new StreamReader(path))      
        return int.Parse(file.ReadLine());
}

试试这个...

class Program
{
    static int? Sum = null;
    static Object lockObject = new Object();
    static void Main()
    {
        Thread t1 = new Thread(ReadFile);
        Thread t2 = new Thread(ReadFile);
        Thread t3 = new Thread(ReadFile);
        t1.Start(@"C:'Users'Mike'Documents'SomeFile1.txt");
        t2.Start(@"C:'Users'Mike'Documents'SomeFile2.txt");
        t3.Start(@"C:'Users'Mike'Documents'SomeFile3.txt");
        t1.Join();
        t2.Join();
        t3.Join();
        if (Sum.HasValue)
        {
            System.Console.WriteLine("Sum: " + Sum.ToString());
        }
        else
        {
            System.Console.WriteLine("No values were successfully retrieved from the files!");
        }
        Console.WriteLine("");
        Console.Write("Press Enter to Quit");
        System.Console.ReadLine();
    }
    public static void ReadFile(Object fileName)
    {
        try
        {
            using (System.IO.StreamReader file1 = new System.IO.StreamReader(fileName.ToString()))
            {
                int x = 0;
                string line = file1.ReadLine();
                if (int.TryParse(line, out x))
                {
                    lock (lockObject)
                    {
                        if (!Sum.HasValue)
                        {
                            Sum = x;
                        }
                        else
                        {
                            Sum = Sum + x;
                        }
                    }
                }
                else
                {
                    Console.WriteLine("Invalid Integer in File: " + fileName.ToString() + "'r'nLine from File: " + line);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception Reading File: " + fileName.ToString() + "'r'nException: " + ex.Message);
        }
    }
}

从@pescolino解释得很好的答案开始,我们可以做一些改进。首先,如果我们假设您的教师确实希望您使用实际的"线程"而不是任务*,我们仍然可以通过使用Interlocked库而不是手动锁定对象来改进代码。这将为我们提供更好的性能,以及(更重要的是)更简单的代码。

private static void ReadIntFromFile(string filename)
{
    string firstLine = System.IO.File.ReadLines(filename).First();
    Interlocked.Add(ref result, int.Parse(firstLine));
}

现在,我不知道您是否已经涵盖了 LINQ - 我知道有时教师不喜欢学生使用他们尚未学习的工具 - 但如果允许,我们可以使主要方法更简单:

private static void Main()
{
    var files = new[]{"File1.txt", "File2.txt", "File3.txt"};
    var threads = files.Select(f => new Thread(() => ReadIntFromFile(f))).ToList();
    threads.ForEach(t => t.Start());
    threads.ForEach(t => t.Join());
    Console.Write("Sum: {0}", result);
    console.ReadLine();
}

现在,让我们看看如果允许我们使用任务,我们将如何改变这一点:

private static void Main()
{
    var files = new[]{"File1.txt", "File2.txt", "File3.txt"};
    var tasks = files.Select(f => Task.Factory.StartNew(() => ReadIntFromFile(f)));
    Task.WaitAll(tasks.ToArray());
    Console.Write("Sum: {0}", result);
    Console.ReadLine();
}

但是你知道,一旦我们使用 LINQ 和 TPL,一种更"实用"的编程方法就会变得更加有利。换句话说,与其让 ReadIntFromFile 方法添加到全局变量 (ick!),不如让它返回它读取的值:

private static int ReadIntFromFile(string filename)
{
    string firstLine = System.IO.File.ReadLines(filename).First();
    return int.Parse(firstLine);
}

现在看看我们可以用 main 方法做什么:

private static void Main()
{
    var files = new[]{"File1.txt", "File2.txt", "File3.txt"};
    int result = files.AsParallel().Sum(f => ReadIntFromFile(f));
    Console.Write("Sum: {0}", result);
    Console.ReadLine();
}

看看如果我们使用所有可用的工具,并行代码会有多简单?

*任务并不总是在单独的线程中运行 - 它们通常共享相同的线程。

可能是

这样:

public static void ReadFile1(ref int? x)
{
    System.IO.StreamReader file1 = new System.IO.StreamReader( {FILEDESTINATION});
    x = int.Parse(file1.ReadLine());
}

并调用它

int? res1 = null;
Thread t1 = new Thread(()=>ReadFile1(ref res1));
//...
t1.Start();
t1.Join();
System.Console.WriteLine("Sum: " + res1);
线程

不返回值。ThreadStartParameterizedThreadStart委托的返回类型为 void

要使用线程执行此操作,您需要将结果存储在某个地方。如果它是共享变量,则需要在更新此值期间lock以避免冲突:

private object lockObj = new object();
private int result;
private static void Main()
{
    result = 0;
    Thread t1 = new Thread(() => ReadIntFromFile("File1.txt"));
    Thread t2 = new Thread(() => ReadIntFromFile("File2.txt"));
    Thread t3 = new Thread(() => ReadIntFromFile("File3.txt"));
    t1.Start();
    t2.Start();
    t3.Start();
    // don't forget to call Join to wait for the end of each thread
    t1.Join();
    t2.Join();
    t3.Join();
    Console.Write("Sum: {0}", result);
    console.ReadLine();
}
private void ReadIntFromFile(string filename)
{
    string firstLine = System.IO.File.ReadLines(filename).First();
    lock (lockObj)
    {
        result += int.Parse(firstLine);
    }
}

lock 关键字确保代码不能由多个线程同时执行。如果您不使用lock则结果可能是错误的。

由于您为每个文件使用不同的方法,因此您当然可以使用不同的结果变量。那么你就不需要锁了。但是,如果您有 100 个文件怎么办?您可能不想编写 100 个方法。

执行此操作的一种更简单的方法是使用 TPL(从 .NET 4 开始)。任务可以具有返回类型,并且更易于管理。我还更改了允许任意数量的文件的方法:

private static void Main()
{
    var sum = SumValuesFromFiles("File1.txt", "File2.txt", "File3.txt");
    Console.Write("Sum: {0}", sum);
    Console.ReadLine();
}
private static int SumValuesFromFiles(params string[] files)
{
    Task<int>[] tasks = new Task<int>[files.Length];
    for (int i = 0; i < files.Length; i++)
    {
        // use a local copy for the parameter because i might get changed before the method is called
        string filename = files[i];
        tasks[i] = Task.Factory.StartNew(() =>
                                         {
                                             string firstLine = System.IO.File.ReadLines(filename).First();
                                             return int.Parse(firstLine);
                                         });
    }
    Task.WaitAll(tasks);
    return tasks.Sum(t => t.Result);
}

简单的答案:你不能。您必须编写收集结果的逻辑代码。即使用全局变量来保持总和。但在更新总和时使用锁定。