我正在使用.Net版本4.6.1和VS2015 Update 3。
我遇到一个奇怪的问题,因为在Debug模式下编译时一段代码可以正常工作,但是在Release模式下编译时,它会失败,并出现NullReferenceException。
我有一个异步方法,该方法使用TaskCompletionSource等待任务完成,如果未收到响应,我将在5秒后再次重试。我已经在下面给出的简化示例代码中重现了此错误。
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TaskCompletionIssue
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
#if DEBUG
this.Text = "Running in Debug Mode";
#else
this.Text = "Running in Release Mode";
#endif
}
private async void btnStartTest_Click(object sender, EventArgs e)
{
try
{
this.Cursor = Cursors.WaitCursor;
await sendRequest("Test");
MessageBox.Show("Test Completed Successfully");
}
finally
{
this.Cursor = Cursors.Default;
}
}
private static TimeSpan secondsToWaitBeforeRetryingRequest = TimeSpan.FromSeconds(5);
private static TimeSpan secondsToWaitForResponse = TimeSpan.FromSeconds(180);
internal static readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> ClientResponses = new ConcurrentDictionary<Guid, TaskCompletionSource<object>>();
private static Thread t1 = null;
public async static Task<object> sendRequest(String req)
{
var tcs = new TaskCompletionSource<object>();
Guid requestId = Guid.NewGuid();
ClientResponses.TryAdd(requestId, tcs);
try
{
DateTime startTime = DateTime.Now;
while (true)
{
//Call method to send request, It doesn't block the thread
SendRequestForProcessing(requestId, req);
if (tcs == null)
{
MessageBox.Show("tcs is null");
}
var task = tcs.Task;
//Wait for the client to respond
if (await Task.WhenAny(task, Task.Delay(secondsToWaitBeforeRetryingRequest)) == task)
{
return await task;
}
else
{
if ((DateTime.Now - startTime).TotalSeconds > secondsToWaitForResponse.TotalSeconds)
{
throw new TimeoutException("Could not detect response within " + secondsToWaitForResponse.TotalSeconds.ToString() + " secs.");
}
else
{
//Let's try again, Previous call might be lost due to network issue
}
}
}
}
finally
{
// Remove the tcs from the dictionary so that we don't leak memory
ClientResponses.TryRemove(requestId, out tcs);
}
}
private static void SendRequestForProcessing(Guid requestId, string req)
{
//Not doing anything with request as this is just a sample program
if (t1 == null || !t1.IsAlive)
{
t1 = new Thread(receivedResponse);
t1.Name = "Test";
t1.IsBackground = true;
t1.Start(requestId);
}
}
public static void receivedResponse(object id)
{
TaskCompletionSource<object> tcs;
Guid requestId = (Guid)id;
if (ClientResponses.TryGetValue(requestId, out tcs))
{
//Some static wait in sample program
Thread.Sleep(TimeSpan.FromSeconds(15));
// Trigger the task continuation
tcs.TrySetResult("Test Success");
}
else
{
throw new Exception($"Request not found for id {requestId.ToString()}");
}
}
}
}
在上面的代码示例中,如果变量' tcs '为空,我已经显示了一条消息。在发布模式下编译代码时,我收到错误消息;尽管如此,它在调试模式下仍然可以正常工作。
此外,要解决此问题,我只是将下面的代码行移至try块之外,一切正常。
var task = tcs.Task;
对我来说,这似乎是某种.NET错误。
有人可以帮助我了解这种尴尬的行为吗?
编辑1:
好吧,这个问题有点难以置信,因此,我创建了一个工作示例项目来再现此问题。请从下面的链接下载它,然后在Debug和Release模式下编译代码。
编译后,请在两种模式下逐一运行可执行文件。
在调试模式下,测试应在15秒后完成,但是,它将显示一条消息,即在释放模式下'tcs'变量为null,并且在按下“确定”按钮时,它将引发NullReferenceException。