我正在使用 .Net 4.6.1 版和 VS2015 Update 3。
我面临一个奇怪的问题,因为一段代码在调试模式下编译时工作正常,但是,在发布模式下编译时它会因 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:
好吧,这个问题有点难以置信,因此,我创建了一个重现此问题的工作示例项目。请从以下链接下载并在调试和发布模式下编译代码。
编译完成后,请以两种模式一一运行可执行文件。
在调试模式下,测试应该在 15 秒后完成,但是,它会显示一条消息,即“tcs”变量在发布模式下为空,并且在按下 Ok 按钮时会抛出 NullReferenceException。