我一直在尝试追踪 Winforms 应用程序中的以下问题:
SynchronizationContext.Current
在主线程上运行的任务延续(即 .ContinueWith
)中为 null(我希望当前同步上下文为 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 将为其事件 RunWorkerCompleted
和 ProgressChanged 使用当前的 SynchronizationContext
。由于当我启动 BackgroundWorker 时当前 SynchronizationContext 为 null,因此事件不会按我的意图在主 ui 线程上运行。
我的问题:
这是 Microsoft 代码中的错误,还是我在某处犯了错误?
附加信息:
- 我正在使用 .Net 4.0(我还没有在 .NET 4.5 RC 上尝试过)
- 我可以在任何 x86/x64/任何 CPU(在 x64 机器上)的调试/发布中重现此内容。
- 它始终如一地复制(如果有人无法复制它,我会很感兴趣)。
- 我有使用 BackgroundWorker 的遗留代码——所以我不能轻易地改成不使用 BackgroundWorker
- 我已确认
MyContinueWith
中的代码正在主 ui 线程上运行。 - 我不知道为什么
StartLogicalOperation
调用会导致问题,这正是我在我的应用程序中缩小范围的原因。
最佳答案
此问题已在 .NET 4.5 RC 中修复(刚刚测试过)。所以我认为这是.NET 4.0 中的错误。 另外,我猜这些帖子引用的是同一个问题:
- How can SynchronizationContext.Current of the main thread become null in a Windows Forms application?
- http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/629d5524-c8db-466f-bc27-0ced11b441ba
真可惜。现在我必须考虑变通办法。
编辑:
通过调试 .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);
}
}
只有当我们进入调用 RunInternal 的“else”子句时,问题才会重现。这是因为 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;
}
}
因此,对于我的具体情况,这是因为 `executionContext.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;
}
对我来说,由于 _logicalCallContext.HasInfo
为真而返回假。这是代码:
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;
}
}
对我来说,这会返回 true,因为 HasUserData 为 true。这是代码:
internal bool HasUserData
{
get { return ((m_Datastore != null) && (m_Datastore.Count > 0));}
}
对我来说,由于我调用了 Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");
总而言之,看起来有几种不同的方法可以让错误重现。希望这个例子能帮助其他人确定他们是否遇到了同样的错误。
关于c# - SynchronizationContext.Current 在主 UI 线程的 Continuation 中为 null,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11621372/