Подтвердить что ты не робот

SynchronizationContext.Current имеет значение null в продолжении в основном потоке пользовательского интерфейса

Я пытаюсь найти следующую проблему в приложении Winforms:
SynchronizationContext.Current имеет значение null в продолжении задачи (т.е. .ContinueWith), который запускается в основном потоке (я ожидаю, что текущий контекст синхронизации будет System.Windows.Forms.WindowsFormsSynchronizationContext).

Здесь код Winforms демонстрирует проблему:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TaskScheduler ts = TaskScheduler.FromCurrentSynchronizationContext(); // Get the UI task scheduler

            // This line is required to see the issue (Removing this causes the problem to go away), since it changes the codeflow in 
            // \SymbolCache\src\source\.NET\4\DEVDIV_TFS\Dev10\Releases\RTMRel\ndp\clr\src\BCL\System\Threading\ExecutionContext.cs\1305376\ExecutionContext.cs
            // at line 435
            System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

            var task = Task.Factory.StartNew(() => { });
            var cont = task.ContinueWith(MyContinueWith, CancellationToken.None, TaskContinuationOptions.None, ts);

            System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation();
        }

        void MyContinueWith(Task t)
        {
            if (SynchronizationContext.Current == null) // The current SynchronizationContext shouldn't be null here, but it is.
                MessageBox.Show("SynchronizationContext.Current is null");
        }
    }
}

Это проблема для меня, поскольку я пытаюсь использовать BackgroundWorker из продолжения, а BackgroundWorker будет использовать текущий SynchronizationContext для своих событий RunWorkerCompleted и ProgressChanged. Поскольку текущий SynchronizationContext имеет значение null, когда я запускаю BackgroundWorker, события не запускаются в основном потоке ui, как я предполагаю.

Мой вопрос:
Это ошибка в коде Microsoft, или я где-то ошибся?

Дополнительная информация:

  • Я использую .Net 4.0 (я еще не пробовал это на .NET 4.5 RC)
  • Я могу воспроизвести это как в Debug/Release на любом из x86/x64/Any CPU (на машине x64).
  • Он воспроизводится последовательно (мне было бы интересно, если кто-то не сможет воспроизвести его).
  • У меня есть устаревший код, который использует BackgroundWorker, поэтому я не могу легко перейти на использование BackgroundWorker
  • Я подтвердил, что код в MyContinueWith запущен в основном потоке ui.
  • Я не знаю точно, почему вызов StartLogicalOperation помогает вызвать проблему, именно то, что я сузил ее в своем приложении.
4b9b3361

Ответ 1

Проблема исправлена ​​в .NET 4.5 RC (только что протестирована). Поэтому я предполагаю, что это ошибка в .NET 4.0. Кроме того, я предполагаю, что эти сообщения ссылаются на одну и ту же проблему:

Это несчастливо. Теперь я должен рассмотреть обходные пути.

Edit:
От отладки в источник .Net я немного лучше понимаю, когда проблема будет воспроизводиться. Вот некоторый соответствующий код из ExecutionContext.cs:

        internal static void Run(ExecutionContext executionContext, ContextCallback callback,  Object state, bool ignoreSyncCtx) 
        {
            // ... Some code excluded here ...

            ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
            if ( (ec == null || ec.IsDefaultFTContext(ignoreSyncCtx)) &&
#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
                SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) && 
#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
                executionContext.IsDefaultFTContext(ignoreSyncCtx)) 
            { 
                callback(state);
            } 
            else
            {
                if (executionContext == s_dummyDefaultEC)
                    executionContext = s_dummyDefaultEC.CreateCopy(); 
                RunInternal(executionContext, callback, state);
            } 
        } 

Проблема только воспроизводится, когда мы попадаем в предложение else, которое вызывает RunInternal. Это связано с тем, что RunInternal заканчивает замену ExecutionContext, который влияет на изменение текущего SynchronizationContext:

        // Get the current SynchronizationContext on the current thread 
        public static SynchronizationContext Current 
        {
            get
            { 
                SynchronizationContext context = null;
                ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate(); 
                if (ec != null) 
                {
                    context = ec.SynchronizationContext; 
                }

                // ... Some code excluded ...
                return context;
            }
        } 

Итак, для моего конкретного случая это произошло потому, что строка `executeContext.IsDefaultFTContext(ignoreSyncCtx)) вернула false. Вот этот код:

        internal bool IsDefaultFTContext(bool ignoreSyncCtx)
        { 
#if FEATURE_CAS_POLICY 
            if (_hostExecutionContext != null)
                return false; 
#endif // FEATURE_CAS_POLICY
#if FEATURE_SYNCHRONIZATIONCONTEXT
            if (!ignoreSyncCtx && _syncContext != null)
                return false; 
#endif // #if FEATURE_SYNCHRONIZATIONCONTEXT
#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK 
            if (_securityContext != null && !_securityContext.IsDefaultFTSecurityContext()) 
                return false;
#endif //#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK 
            if (_logicalCallContext != null && _logicalCallContext.HasInfo)
                return false;
            if (_illogicalCallContext != null && _illogicalCallContext.HasUserData)
                return false; 
            return true;
        } 

Для меня возвращаемое значение false из-за _logicalCallContext.HasInfo было true. Вот этот код:

public bool HasInfo
{ 
    [System.Security.SecurityCritical]  // auto-generated
    get
    {
        bool fInfo = false; 

        // Set the flag to true if there is either remoting data, or 
        // security data or user data 
        if(
            (m_RemotingData != null &&  m_RemotingData.HasInfo) || 
            (m_SecurityData != null &&  m_SecurityData.HasInfo) ||
            (m_HostContext != null) ||
            HasUserData
          ) 
        {
            fInfo = true; 
        } 

        return fInfo; 
    }
}

Для меня это вернулось, потому что HasUserData было правдой. Вот этот код:

    internal bool HasUserData
    {
        get { return ((m_Datastore != null) && (m_Datastore.Count > 0));} 
    }

Для меня в m_DataStore будут элементы из-за моего вызова Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

В целом, похоже, что существует несколько разных способов получить ошибку для воспроизведения. Надеюсь, этот пример поможет другим людям определить, запущены ли они в эту же ошибку или нет.