如何在多线程c#中使用委托和事件

时间:2017-10-25 20:19:23

标签: c# multithreading

我正在尝试多线程编码。如果我想并行执行进程。说,我想同时查询表A,表B和表C.在每个线程中,我正在执行SqlCommand .ExecuteReader,如下所示:

使用System.Collections.Generic; 使用System.Data.SqlClient;

namespace MultiThread2
{
    public class TableFieldArray
    {
        public string TableName { get; set; }
        public string FieldName { get; set; }
    }

    static class MTProcess
    {
        const string connStr = "server=***;database=***;user id=***;password=***";

        public static event GetStringArrayResult OnRecordsFoundMTS;

        public static void GetStringArrays(TableFieldArray[] tableFieldArray)
        {
            foreach (TableFieldArray tf in tableFieldArray)
            {
                Thread thread = new Thread(() =>
                {
                    ST_Process stp = new ST_Process(connStr);
                    stp.ListOfFieldValue(tf.TableName, tf.FieldName);
                    stp.OnRecordsFound += new GetStringArrayResult(getEvent);
                });

                thread.Start();
            }
        }

        private static void getEvent(string[] result)
        {
            OnRecordsFoundMTS(result);
            return;
        }

// ---------------

    public delegate void GetStringArrayResult(string[] output);

    class ST_Process
    {
        public event GetStringArrayResult OnRecordsFound;

        private readonly string _connStr;

        public ST_Process(string connectionString)
        {
            _connStr = connectionString;
        }

        public void ListOfFieldValue(string tableName, string fieldName)
        {
            List<string> result = new List<string>();
            using (SqlConnection sqlConn = new SqlConnection(_connStr))
            {
                sqlConn.Open();
                string sqlText = string.Format("SELECT TOP 100 {0} FROM {1} ", fieldName, tableName);
                using (SqlCommand sqlcmd = new SqlCommand(sqlText, sqlConn) { CommandType = System.Data.CommandType.Text })
                {
                    var r = sqlcmd.ExecuteReader();
                    while (r.Read())
                    {
                        result.Add(r[fieldName].ToString());
                    }
                    OnRecordsFound(_result.ToArray());
                }
                sqlConn.Close();
            }
        }
    }
}

问题是,当在单独的线程中执行时,OnRecordsFound(_result.ToArray())会导致对象引用未设置为对象异常错误的实例。知道如何使这个设置适用于多线程环境吗?

3 个答案:

答案 0 :(得分:2)

Thread thread = new Thread(() =>
{
    ST_Process stp = new ST_Process(connStr);
    stp.OnRecordsFound += new GetStringArrayResult(getEvent);
    stp.ListOfFieldValue(tf.TableName, tf.FieldName);
});

您需要先订阅该活动。此外,每当调用事件时,最好检查null:

var e = someEvent;
if (e != null)
    e();

答案 1 :(得分:2)

写入数据的List<T>类的实例方法(例如Add)不是线程安全的,因此不能被多个线程使用。

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.1#Thread_Safety

  

线程安全

     

此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。不保证任何实例成员都是线程安全的。

     

在List上执行多个读取操作是安全的,但如果在读取集合时修改了集合,则可能会出现问题。要确保线程安全,请在读取或写入操作期间锁定集合。要使多个线程可以访问集合以进行读写,您必须实现自己的同步。对于具有内置同步的集合,请参阅System.Collections.Concurrent命名空间中的类。

相反,您应该使用线程安全的集合类,例如System.Collections.Concurrent.ConcurrentQueue

https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/

答案 2 :(得分:0)

快速浏览一下,假设您的三个线程正在同一个对象上调用ListOfFielfValue,看起来您的多个线程都写入共享变量_result。您需要在方法中将其设为局部变量。

跨线程共享对象。如果您希望多个线程访问同一个对象,那么您将需要某种锁定。