在发布模式下编译代码时,TaskCompletionSource对象在异步方法中为null

时间:2019-11-01 12:21:01

标签: c# .net taskcompletionsource

我正在使用.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模式下编译代码。

suggest

Download Sample

编译后,请在两种模式下逐一运行可执行文件。

在调试模式下,测试应在15秒后完成,但是,它将显示一条消息,即在释放模式下'tcs'变量为null,并且在按下“确定”按钮时,它将引发NullReferenceException。

0 个答案:

没有答案