2

我正在使用 .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。

4

0 回答 0