简体   繁体   中英

Return value from thread?

I'm very new to programming, hope you can help me. My assignment is to make 3 different threads which reads a txt file with a given int. It must then print out the sum of these values. I want to access the int from the three threads i've made. How can i do this?

This is some of my code:

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());

    }

The tasking system in .NET makes this very easy. You should prefer it to raw threads in almost all cases. For your example:

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());
}

Try this out...

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

}

Beginning with @pescolino's very well-explained answer, there are some improvements that we can make. First of all, if we assume that your instructor actually does want you to use actual "Threads" instead of Tasks * , we can still improve the code by using the Interlocked library instead of locking on an object manually. This will give us better performance, and (more importantly) simpler code.

private static void ReadIntFromFile(string filename)
{
    string firstLine = System.IO.File.ReadLines(filename).First();

    Interlocked.Add(ref result, int.Parse(firstLine));
}

Now, I don't know if you've covered LINQ yet--I know sometimes instructors don't like students using tools they haven't gone over yet--but if you're allowed to, we can make the main method much simpler:

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();
}

Now, let's examine how we might change this if we were allowed to use Tasks after all:

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();
}

But you know, once we're using LINQ and the TPL, a more "functional" approach to programming becomes more favorable. In other words, rather than having the ReadIntFromFile method add to a global variable (ick!), let's have it return the value that it reads:

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

Now look what we can do with the main method:

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();
}

See how simple parallel code can be, if we use all the tools available to us?

*Tasks don't always run in separate threads--they often share the same threads.

May be some kind of this:

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

and invoke it with

int? res1 = null;
Thread t1 = new Thread(()=>ReadFile1(ref res1));
//...
t1.Start();
t1.Join();

System.Console.WriteLine("Sum: " + res1);

Threads do not return values. The ThreadStart and ParameterizedThreadStart delegates have return type void .

To do this with threads you need to store the result somewhere. If it is a shared variable you need a lock during updating this value to avoid conflicts:

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

The lock keyword makes sure that the code can not be executed by multiple threads at the same time. If you wouldn't use a lock the result might be wrong.

Since you are using different methods for each file you could of course use different result variables. Then you wouldn't need a lock. But what if you have 100 files? You probably don't want to write 100 methods.

A much easier way to do this is to use the TPL (since .NET 4). Tasks can have a return type and are easier to manage. I also changed the approach to allow any number of files:

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

Simple answer: You can't. You'll have to code logic that collects the result. Ie use a global variable to keep the sum. But use locking while the sum is updated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM