2
\$\begingroup\$

Is this thread safe?

I have a program that executes an SQL command on a number of files (selected by the user).

Here is where I create the threads:

var allThreads = new List<Task<bool>>();
if (fileList != null)
{

    foreach (var file in fileList)
    {
        var fileName = Path.GetFileName(file);
        if (File.Exists(file))
        {
            Log.AddToLog("Beginning to Process File: " + fileName);
            if (!ValidateFile(file))
            {
                Log.AddToLog(String.Format("Error! {0} is not a valid file for the {1}. Skipping this file.", fileName, JsonSettingsList.NameId));
                IsErrorsInLog = true;
            }
            else
            {
                var fileToProcess = file; //need to make copy for threading loop, otherwise it acts screwy
                var newTask = Task.Factory.StartNew<bool>(() => ProcessFile(fileToProcess));
                allThreads.Add(newTask);
            }
        }
        else
        {
            Log.AddToLog(String.Format(CultureInfo.CurrentCulture, "File {0} Does not Exist. File not processed.", fileName));
            IsErrorsInLog = true;
        }
    }


    //check result, if any of them return true, then there are errors in the log, and the user needs to be alerted.
    foreach (var task in allThreads)
    {
        try
        {
            var didThreadFail = task.Result;
            if (!IsErrorsInLog)
            {
                IsErrorsInLog = didThreadFail;
            }
        }
        catch (AggregateException aggEx)
        {
            foreach (Exception ex in aggEx.InnerExceptions)
            {
                Log.AddToLog(string.Format("Caught exception '{0}'",
                    ex.Message));
                IsErrorsInLog = true;
            }
        }
        finally
        {
            task.Dispose();
        }
    }
}

and then here is the code where I am actually accessing the database, inside of the ProcessFile() function:

//initialize oracle connection 
oraConnection.Open();
using (var command = new OracleCommand(sqlText, oraConnection))
{
    command.Transaction = command.Connection.BeginTransaction();
    foreach (var line in fileByLine.Where((i, index) => index > 0))
    {
        if (!String.IsNullOrWhiteSpace(line))
        {
            //add parameters
            command.ExecuteNonQuery();
        }
    }

    command.Transaction.Commit();
    Log.AddToLog("Successfully Processed " + filePath);
}

oraConnection.Close();

The comments are where I have omitted unnecessary code for the sake of simplicity.

\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$
if (fileList != null)

You can reduce nesting (which is usually a good thing) by turning this into

  if (fileList == null) { return; }

The same applies to

if (File.Exists(file))

It's better to have

if (!File.Exists(file)) { continue; }

But even better then this would be to filter the list before you start processing it:

fileList.Where(File.Exists)

Checking the fileList for null is an indicator that some other part of the code might be implemented in a wrong way_. In C# we prefer to have an empty list rather then a null.

Parallel.ForEach

Have you already tried the Parallel.ForEach? It would greatly simplify your code:

Parallel.ForEach(fileList.Where(File.Exists), fileName => 
{
    // process the file
});

This method has a lot of overloads so you'll probably find something that suits your needs.

With one of them you could for example create a connection for each parallel loop and dispose it at the end of the processing.

Here's a bigger example that might work for you. This will use the same connection for several files.

try
{
    var parallelOptions = new ParallelOptions
    {
#if DEBUG
        MaxDegreeOfParallelism = 1
#else
        MaxDegreeOfParallelism = Environment.ProcessorCount
#endif
    };

    var parallelLoopResult = Parallel.ForEach
    (
        source: fileList.Where(File.Exists),
        parallelOptions: parallelOptions,
        localInit: () => new
        {
            Connection = /* init the oracle connection*/                
        },
        body: (fileName, loopState, i, local) =>
        {
            // process the file
            // use the local.Connection
            return local; // pass the connection to the next loop
        },
        localFinally: local =>
        {
            local.Connection.Dispose();             
        }
    );
}
catch (AggregateException ex)
{
    // handle exceptions
}

See this question on SO, it compares your solution with Parallel.ForEach:

\$\endgroup\$
5
  • \$\begingroup\$ So do something like (using var connection ){ Parallel.ForEach(blablabla) { ProcessFile() } } \$\endgroup\$ Commented Nov 8, 2016 at 18:13
  • \$\begingroup\$ @JacobAlley almost, if there is no reason to write everything by hand the Parallel.ForEach or Parallel.For are really handy. I'll add an example... \$\endgroup\$ Commented Nov 8, 2016 at 18:17
  • \$\begingroup\$ thank you for all of your help. So using Parallel essentially will replace my Task<bool> list. I appreciate all of your cleanup tips and I will certainly implement them. However, as it stands right now, without any changes, is the code that I provided thread safe? \$\endgroup\$ Commented Nov 8, 2016 at 18:19
  • \$\begingroup\$ @JacobAlley I've added another example. You solution looks good and virtually does the same but with a lot more of manual work. See also the link. \$\endgroup\$ Commented Nov 8, 2016 at 18:31
  • \$\begingroup\$ You can even combine both solutions and use the advantages of either one. \$\endgroup\$ Commented Nov 8, 2016 at 18:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.