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



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

我尝试过使用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

如何使"success"为"true"的线程等待死锁线程完成?

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

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

当你使用一个屏障时,它基本上意味着你分配到屏障中的所有线程都必须达到一个";检查点";然后每一个都可以继续。

下面是一个可能的快速示例:

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时,当两个任务必须相互等待时,您会遇到问题。在您的示例中,您希望在当前线程完成其工作时等待(Join(,但另一个线程没有。我们怎么知道另一个线程什么时候完成了它的工作?

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

我们可以对sentinel值使用很多东西,包括封装变量、类变量,甚至WaitHandles

如果我们使用sentinel值,我们可以通过向每个线程传递对彼此的引用来使用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();
}
}
}

最新更新