多线程:等待线程完成以便我可以重试死锁

时间:2021-05-27 15:52:15

标签: c# sql .net multithreading forms

我有 2 个线程,我必须让成功的线程等待,而死锁的线程会重试。当前代码只适用于第一个循环,因为成功的线程退出了。

我尝试使用 Thread.Join() 没有成功。

如何让 'success' 为 'true' 的线程等待死锁线程完成?

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread1 = new Thread(new ThreadStart(procedure1));
            Thread thread2 = new Thread(new ThreadStart(procedure2));

            thread1.Start();
            thread2.Start();
        }


        private void procedure1()
        {
            Console.WriteLine("thread 1");
            bool success = false;
            int retryCount = 1;
            while ((retryCount <= 3) && success == false)
            {
                try
                {
                    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                    {
                        SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                        connection.Open();
                        cmd.ExecuteNonQuery();
                        success = true;
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 1205)
                    {
                        if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                        Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                        retryCount++;
                    }
                    else
                        Console.WriteLine(ex.Message);
                }
            }
        }

        private void procedure2()
        {
            Console.WriteLine("thread 2");
            bool success = false;
            int retryCount = 1;
            while ((retryCount <= 3) && success == false)
            {
                try
                {
                    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                    {
                        SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                        connection.Open();
                        cmd.ExecuteNonQuery();
                        success = true;
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 1205)
                    {
                        if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                        Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                        retryCount++;
                    }
                    else
                        Console.WriteLine(ex.Message);
                }
            }
        }
    }

用于死锁的 SQL 程序:

CREATE OR ALTER PROCEDURE deadlockP1
AS
BEGIN
    BEGIN TRAN;
        UPDATE Employee SET position = 'handler' WHERE employee_code = 4037;
        WAITFOR DELAY '00:00:05';
        UPDATE Shop SET shop_name = 'TITANUS' WHERE shop_code = 2019;
    COMMIT TRAN;    
END

CREATE OR ALTER PROCEDURE deadlockP2
AS
BEGIN
    BEGIN TRAN;
        UPDATE Shop SET shop_name = 'TITANUS' WHERE shop_code = 2019;
        WAITFOR DELAY '00:00:05';
        UPDATE Employee SET position = 'manager' WHERE employee_code = 4037;
    COMMIT TRAN;    
END

1 个答案:

答案 0 :(得分:0)

<块引用>

如何让 'success' 为 'true' 的线程等待死锁线程完成?

如果所有线程必须在相似的时间完成并相互等待,请考虑使用 Barrier。否则我建议使用 Join(见下文)。

<块引用>

一组任务通过一系列阶段进行协作,其中组中的每个任务都表示它在给定阶段已到达 Barrier 并隐式等待所有其他任务到达。同一屏障可用于多个阶段。

当您使用屏障时,它基本上意味着您分配给屏障的所有线程都必须在每个线程才能继续之前都到达“检查点”。

下面是一个简单的例子:

public partial class Form1 : Form
{
    // create a barrier with 2 slots
    private Barrier barrier = new Barrier(2);

    private void procedure1()
    {
        Console.WriteLine("thread 1");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                    Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for all other tasks in barrier to get to this checkpoint before returning
        barrier.SignalAndWait();
    }

    private void procedure2()
    {
        Console.WriteLine("thread 2");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                    Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for all other tasks in barrier to get to this checkpoint before returning
        barrier.SignalAndWait();
    }
}

应该注意的是,虽然使用 Task.WhenAll(async) 和 Task.WaitAll(sync) 可以实现类似的效果,但它们与 Join 一起更容易实现(但你说你不想使用/不能使用join)。

如果您想重新考虑使用 Join,请考虑将您要等待的线程的引用传递给另一个线程。 Join 阻塞当前线程(等待),而它调用 Join 的线程的实例完成。

简而言之,Join 是如何工作的,只要确保不要让两个线程试图相互等待即可。你会等他们很长时间才能弄清楚。

void ThreadOne(Thread other)
{
    // do work
    // ...
    
    // wait for other to finish
    other?.Join();
}

void ThreadTwo()
{
    // do work
    // ...
}

当您使用 Join 时,您会遇到两个任务必须相互等待的问题。在您的示例中,您希望在当前线程完成工作时等待(加入),但另一个没有。我们如何知道另一个线程何时完成了它的工作?

我们应该使用 sentinel 值来通知其他线程工作已经完成或仍需要完成。

我们可以对标记值使用很多东西,从封装变量、类变量甚至 WaitHandles

如果我们使用哨兵值,我们可以通过向每个线程传递一个引用来使用 Join

这可能是这样的:

public partial class Form1 : Form
{
    // create sentinels
    bool thread1Status = false;
    bool thread2Status = false;

    // create a spot to store the threads so they can reference each other
    Thread thread1;
    Thread thread2;


    private void Form1_Load(object sender, EventArgs e)
    {
        thread1 = new Thread(new ThreadStart(procedure1));
        thread2 = new Thread(new ThreadStart(procedure2));

        thread1.Start();
        thread2.Start();
    }

    private void procedure1()
    {
        Console.WriteLine("thread 1");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                    
                    // notify other thread that we finished work
                    thread1Status = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                    Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for the other thread to finish work if it hasn't
        if(success && thread2Status == false)
        {
             thread2?.Join();
        }
    }

    private void procedure2(Thread other)
    {
        Console.WriteLine("thread 2");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;

                    // notify other thread that we finished work
                    thread2Status = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                    Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for the other thread to finish work if it hasn't
        if(success && thread1Status == false)
        {
             thread1?.Join();
        }
    }
}