【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext
一、同步上下文(SynchronizationContext)概述
由来
多线程程序在.net框架出现之前就已经存在了。这些程序通常需要一个线程将一个工作单元传递给另一个线程。Windows程序以消息循环为中心,因此许多程序员使用这个内置队列来传递工作单元。每个想要以这种方式使用Windows消息队列的多线程程序都必须定义自己的自定义Windows消息和处理它的约定。
当.net框架首次发布时,这种通用模式被标准化了。那时,. net支持的唯一GUI应用程序类型是Windows窗体。然而,框架设计者预期了其他模型,他们开发了一个通用的解决方案。ISynchronizeInvoke诞生了。而SynchronizationContext就是用来取代ISynchronizeInvoke。
1、概念
同步上下文:这里同步是动词,据有归类的功能,假如有A(UI线程)、B类两线程,B线程要更新A线程的内容。如果直接在B中更新A中内容,那就是B线程多管闲事了,增加程序的耦合。为了降低程序的耦合度,B线程必须把更新A线程UI的事情还给A线程, 以消息方式把
要更改内容发送给A线程,A线程有一个堆栈用来保存B线程发送过来的消息。然后A线程根据自己情况决定什么时候更新。
如果B线程以Send()方法给A线程发送消息,B线程发送消息后什么事情都不做一直等待A线程的回复(同步),对应SynchronizationContext.Send()方法。
如果B线程以Post()方法给A线程发送消息,B线程发送完消息后就去做其他事情了(异步)),对应SynchronizationContext.Post()方法(asp.net 除外)。
同步上下文是一种可以将工作单元(执行某些方法 多播委托)排队到上下文(主要是不同的线程)的方法。
它的作用通俗来讲就是实现线程之间通讯的。
同步上下文应用于很多场景,比如在WinForms和WPF中,只有一个UI线程可以更新UI元素(文本框,复选框等)。如果尝试从另一个非UI线程更改文本框的内容,则不会发生更改,也可能抛出异常(取决于UI框架)。因此,在这样的应用程序中,非UI线程需要将对UI元素的所有更改安排到UI线程。这就是同步上下文提供的内容。它允许将一个工作单元(执行某些方法)发布到不同的上下文
- 在这种情况下是UI线程。
注意:cpu每30毫秒切换一次
2、作用域
无论是什么平台(ASP.NET、Windows 窗体、Windows Presentation Foundation (WPF)、Silverlight 或其他),所有 .NET 程序都包含 SynchronizationContext 概念。
Microsoft
.NET
Framework提供了同步上下文的SynchronizationContext类。根据平台框架不同,又单独提供了WindowsFormsSynchronizationContext(WinForm)类、DispatcherSynchronizationContext(WPF)类等同步上下文的模型但都是继承自SynchronizationContext类。
每个线程都有一个默认的SynchronizationContext,但是不是每个线程都附加SynchronizationContext.Current这个对象,只有UI线程是一直拥有的SynchronizationContext.Current。故获取SynchronizationContext.Current也只能在UI线程上进行SynchronizationContext context = SynchronizationContext.Current;
//只有UI线程能获取到 值,这是创建一个副本,不同ExecutionContent.capture,
SynchronizationContext maincontent= SynchronizationContext.Current;//这是创建一个副本。ExecutionContent.capture是捕获引用
//asp.net 和控制获取的结果是null
SynchronizationContext maincontent= SynchronizationContext.Current;
3、原理
通过UI线程与工作线程的时序图可以看出整个更新的步骤:
整个过程中关键的是主线程的SynchronizationContext,SynchronizationContext在通讯中充当传输者的角色。在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。
4、作用:传输者
传输者SynchronizationContext在通讯中充当传输者的角色(用Post/Send方法实现传输),实现功能就是一个线程和另外一个线程的通讯。SynchronizationContext将UI线程的同步环境保存下来,让这个环境可以在不同的线程之间流动,其他非UI线程可以用这个环境回到要ui线程执行的任务。例如在winform应用中,非UI线程中利用这个环境更新UI控件的内容,而不是利用控件的invoke方法。 SynchronizationContext.post()表示启用一个新线程来执行委托(异步执行)。SynchronizationContext.send()表示在当前线程执行(同步的)。SynchronizationContext.post是同步上下文最重要的一个方法。
Send:发送界面更新请求至主线程,阻塞当前线程直至返回。SynchronizationContext.Send(SendOrPostCallback d,object state):
Post:发送界面更新请求至主线程,不阻塞当前线程。SynchronizationContext.Post(SendOrPostCallback d,object state);
public
delegate void SendOrPostCallback(object state);d
为一个没有返回值,并且具有一个Object类型传入参数的委托(SendOrPostCallback );state
为执行这个委托时的参数(object);
注意:
SynchronizationContext的对象不是所有线程都被附加的,只有UI主线程会被附加。
对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?
在Form1 form = new Form1()之前,SynchronizationContext对象是为空,而当实例化Form1窗体后,SynchronizationContext对象就被附加到这个线程上了。
所以可以得出答案了:当Control对象被创建的同时,SynchronizationContext对象也会被创建并附加到线程上。所以在使用时,一定要等窗体InitializeComponent(); 这个完成后 它才能得到一个不是NULL的对象.
5、应用:那么如何编写我的组件与UI框架无关呢?
看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用Control.Invoke, Control.BeginInvoke。这些都是很好的解决方案,不过有两个问题:
1. 必须的引用System.Windows.Forms,然后 using System.Windows.Forms
2. 代码结构比较零乱。(实际上这个也是#1 导致的)
微软提供了另外一个比较优雅的解决方案,就是 System.Threading. SynchronizationContext。 可以看出,它不在
namesapce System.Windows.Forms 里面,因此我们可以理直气壮地用在BusinessLaryer,
Controler,甚至 module 里面。
其实在UI线程中使用的并不是SynchronizationContext这个类,而是WindowsFormsSynchronizationContext这个东东,它是SynchronizationContext的派生类。
以下是SynchronizationContext在winform中实际应用:
案例一
案例二
二、同步上下文的派生
同步上下文的实际"上下文"没有明确定义。不同的框架和主机可以自由地定义自己的上下文。了解这些不同的实现及其局限性,可以准确地阐明同步上下文概念的作用和不足之处。我将简要讨论其中的一些实现。
1、WinForm 的同步上下文
WindowsFormsSynchronizationContext继承自SynchronizationContext
命名空间:System.Windows.Forms.dll:System.Windows.Forms
实现:
Windows Forms应用程序将创建WindowsFormsSynchronizationContext并将其安装为创建UI控件的任何线程的当前上下文。
此SynchronizationContext在UI控件上使用ISynchronizeInvoke方法,该控件将委托传递到基础Win32消息循环。
WindowsFormsSynchronizationContext的上下文是单个UI线程(单线程的)。
排队到WindowsFormsSynchronizationContext的所有委托一次执行一次;它们由特定的UI线程按排队顺序执行。当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。
为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:
async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}
2、WPF框架中同步上下文
源代码
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using MS.Win32; public sealed class DispatcherSynchronizationContext : SynchronizationContext
{
internal Dispatcher _dispatcher; private DispatcherPriority _priority; public DispatcherSynchronizationContext()
: this(Dispatcher.CurrentDispatcher, DispatcherPriority.Normal)
{
} public DispatcherSynchronizationContext(Dispatcher dispatcher)
: this(dispatcher, DispatcherPriority.Normal)
{
} public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
}
Dispatcher.ValidatePriority(priority, "priority");
_dispatcher = dispatcher;
_priority = priority;
SetWaitNotificationRequired();
} public override void Send(SendOrPostCallback d, object state)
{
if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
{
_dispatcher.Invoke(DispatcherPriority.Send, d, state);
}
else
{
_dispatcher.Invoke(_priority, d, state);
}
} public override void Post(SendOrPostCallback d, object state)
{
_dispatcher.BeginInvoke(_priority, d, state);
} public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (_dispatcher._disableProcessingCount > 0)
{
return UnsafeNativeMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll, millisecondsTimeout, false);
}
return SynchronizationContext.WaitHelper(waitHandles, waitAll, millisecondsTimeout);
} public override SynchronizationContext CreateCopy()
{
if (BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance())
{
return this;
}
if (BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority())
{
return new DispatcherSynchronizationContext(_dispatcher, _priority);
}
return new DispatcherSynchronizationContext(_dispatcher, DispatcherPriority.Normal);
}
}
DispatcherSynchronizationContext继承自SynchronizationContext
命名空间:WindowsBase.dll:System.Windows.Threading
实现:
Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。
WPF和Silverlight应用程序使用DispatcherSynchronizationContext,该代理将对UI线程的Dispatcher的委托以“Normal”优先级排队。
当线程通过调用Dispatcher.Run开始循环调度器 ,将这个初始化完成的 同步上下文 安装到当前上下文。
DispatcherSynchronizationContext的上下文是单个UI线程(单线程的。
排队到DispatcherSynchronizationContext的所有委托均由特定的UI线程一次按其排队的顺序执行。当前实现为每个顶级窗口创建一个DispatcherSynchronizationContext,即使它们都共享相同的基础Dispatcher。
为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:
async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}
3、默认同步上下文
源代码
// System.Threading.SynchronizationContext
using System;
using System.Runtime.CompilerServices;
using System.Threading; [NullableContext(1)]
[Nullable(0)]
public class SynchronizationContext
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c(); [TupleElementNames(new string[] { "d", "state" })]
public static Action<ValueTuple<SendOrPostCallback, object>> <>9__8_0; internal void <Post>b__8_0([TupleElementNames(new string[] { "d", "state" })] ValueTuple<SendOrPostCallback, object> s)
{
s.Item1(s.Item2);
}
} private bool _requireWaitNotification; [Nullable(2)]
public static SynchronizationContext Current
{
[NullableContext(2)]
get
{
return Thread.CurrentThread._synchronizationContext;
}
} private static int InvokeWaitMethodHelper(SynchronizationContext syncContext, IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
} protected void SetWaitNotificationRequired()
{
_requireWaitNotification = true;
} public bool IsWaitNotificationRequired()
{
return _requireWaitNotification;
} public virtual void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
d(state);
} public virtual void Post(SendOrPostCallback d, [Nullable(2)] object state)
{
ThreadPool.QueueUserWorkItem(<>c.<>9__8_0 ?? (<>c.<>9__8_0 = new Action<ValueTuple<SendOrPostCallback, object>>(<>c.<>9.<Post>b__8_0)), new ValueTuple<SendOrPostCallback, object>(d, state), false);
} public virtual void OperationStarted()
{
} public virtual void OperationCompleted()
{
} [CLSCompliant(false)]
public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
} [CLSCompliant(false)]
protected static int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (waitHandles == null)
{
throw new ArgumentNullException("waitHandles");
}
return WaitHandle.WaitMultipleIgnoringSyncContext(waitHandles, waitAll, millisecondsTimeout);
} [NullableContext(2)]
public static void SetSynchronizationContext(SynchronizationContext syncContext)
{
Thread.CurrentThread._synchronizationContext = syncContext;
} public virtual SynchronizationContext CreateCopy()
{
return new SynchronizationContext();
}
}
(默认)SynchronizationContext位于:mscorlib.dll:System.Threading
Default SynchronizationContext
是默认构造的 SynchronizationContext
对象。
- 根据惯例,如果一个线程的当前 SynchronizationContext 为 null,那么它隐式具有一个
Default SynchronizationContext
。 Default SynchronizationContext
将其异步委托列队到ThreadPool
,但在调用线程上直接执行其同步委托。- 因此,
Default SynchronizationContext
涵盖所有ThreadPool
线程以及任何调用Send
的线程。 - 这个上下文“借助”调用
Send
的线程们,将这些线程放入这个上下文,直至委托执行完成- 从这种意义上讲,默认上下文可以包含进程中的所有线程。
Default SynchronizationContext
应用于 线程池 线程,除非代码由 ASP.NET 承载。Default SynchronizationContext
还隐式应用于显式子线程(Thread 类的实例),除非子线程设置自己的SynchronizationContext
。
因此,UI 应用程序通常有两个同步上下文:
- 包含 UI 线程的
UI SynchronizationContext
- 包含 ThreadPool 线程的
Default SynchronizationContext
默认SynchronizationContext是多线程的
默认的同步上下文只是将任务安排到线程池操作队列
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
} public virtual void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
d(state);
}
public delegate void SendOrPostCallback(object state);
设置同步上下文
using System;
using System.Threading;
using System.Threading.Tasks; namespace GetSetContext
{
internal static class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}"); SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext()); Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}"); await Task.Delay(100); Console.WriteLine("Completed!");
} private class MySynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine($"Continuation dispatched to {nameof(MySynchronizationContext)}");
d.Invoke(state);
}
}
}
}
这段代码输出以下内容:
Current Synchronization Context: null
Current Synchronization Context: MySynchronizationContext
Continuation dispatched to MySynchronizationContext
Completed!
上下文捕获和执行
BackgroundWorker
运行流程
- 首先
BackgroundWorker
捕获并使用调用RunWorkerAsync
的线程的 同步上下文 - 然后,在
Default SynchronizationContext
中执行DoWork
- 最后,在之前捕获的上下文中执行其
RunWorkerCompleted
事件
UI同步上下文 中只有一个 BackgroundWorker
,因此 RunWorkerCompleted
在 RunWorkerAsync
捕获的 UI同步上下文中执行(如下图)。
UI同步上下文中的嵌套 BackgroundWorker
- 嵌套:
BackgroundWorker
从其DoWork
处理程序启动另一个BackgroundWorker
- 嵌套的
BackgroundWorker
不会捕获 UI同步上下文
- 嵌套的
DoWork
由 线程池 线程使用 默认同步上下文 执行。- 在这种情况下,嵌套的
RunWorkerAsync
将捕获默认SynchronizationContext
- 因此它将由一个 线程池 线程而不是 UI线程 执行其
RunWorkerCompleted
- 这样会导致异步执行完后,后面的代码就不在UI同步上下文中执行了(如下图)。
- 在这种情况下,嵌套的
默认情况下,控制台应用程序 和 Windows服务 中的所有线程都只有 Default SynchronizationContext
,这会导致一些基于事件的异步组件失败(也就是没有UI同步上下文的特性)
- 要解决这个问题,可以创建一个显式子线程,然后将 UI同步上下文 安装在该线程上,这样就可以为这些组件提供上下文。
Nito.Async
库的ActionThread
类可用作通用同步上下文实现。
二、ExecutionContext 上下文的捕获和恢复
三、ExecutionContext 概述
1、ExecutionContext 实际上只是其他上下文的容器。因此在.net framework中ExecutionContext 包含:同步上下文、安全上下文、调用上下文、模拟上下文、区域性通常会与执行上下文一起流动。
2、在.net core中, 不支持安全上下文和调用上下文、同步上下文和ExecutionContex一起流动。
1、2两条内容来源:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.executioncontext?view=net-6.0
ExecutionContext 实际上是一个 state 包
- 用于从一个线程上捕获所有 state
- 然后在控制逻辑流的同时将其还原到另一个线程
ExecutionContext 是使用静态方法 Capture 捕获的:
// 周围环境的 state 捕获到 ec 中
ExecutionContext ec = ExecutionContext.Capture();
通过静态方法 Run ,在委托(Run方法的参数)调用时恢复 ExecutionContext
ExecutionContext.Run(ec, delegate
{
… // 这里的代码将上述 ec 的状态视为周围环境
}, null);
所有派生异步工作的方法都以这种方式捕获和还原 ExecutionContext 的。
- 带有“Unsafe”字样的方法除外,它们是不安全的,因为它们不传播 ExecutionContext
例如:
- 当您使用
Task.Run
时,对Run
的调用将从调用线程中捕获 ExecutionContext ,并将该 ExecutionContext 实例存储到Task
对象中 - 当提供给
Task.Run
的委托作为该Task
执行的一部分被调用时,它是使用存储的 ExecutionContext 通过ExecutionContext.Run
来完成的
以下所有异步API的执行都是捕获 ExecutionContext 并将其存储,然后在调用某些代码时再使用存储的 ExecutionContext。
Task.Run
ThreadPool.QueueUserWorkItem
Delegate.BeginInvoke
Stream.BeginRead
DispatcherSynchronizationContext.Post
- 任何其他异步API
当我们谈论“flowing ExecutionContext”时,我们实际上是在讨论:
- 在一个线程上获取周围环境状态
- 在稍后的某个时刻将该状态恢复到另一个线程上(需要执行提供的委托的线程)。
ExecutionContext类创建副本以便传播
- ExecutionContext无法在另一个线程上设置与线程关联的。 尝试这样做将导致引发异常。 若要将 ExecutionContext 从一个线程传播到另一个线程,请创建的副本 ExecutionContext 。
- ExecutionContext类提供 CreateCopy 创建当前执行上下文的副本。
三. ExecutionContext 和 SynchronizationContext使用区别
前面我们介绍了 SynchronizationContext
是如何调度线程的,现在,我们要进行进行一次对比:
- flowing ExecutionContext 在语义上与 capturing and posting to a SynchronizationContext 完全不同。
ExecutionContext 的流动无法控制、这是框架故意这样设计的,开发人员在编写异步代码时不必担心 ExecutionContext ;它在基础架构级别上的支持,有助于在异步环境中模拟同步方式的语义(即TLS);
SynchronizationContext的流动是可以控制的,可以通过
task.ConfigureAwait(bool) 传入false关闭为 false ,则等待者(awaiter) 不检查 SynchronizationContext ,就像没有一样
- 当 ExecutionContext 流动时,您是从一个线程捕获 state ,然后还原该 state
- 使提供的委托执行时处于周围环境 state
- 当您捕获并使用 SynchronizationContext 时,不会发生这种情况。
- 捕获部分是相同的,因为您要从当前线程中获取数据,但是随后用不同方式使用 state
SynchronizationContext.Post
只是使用捕获的状态来调用委托,而不是在调用委托时设置该状态为当前状态- 该委托在何时何地以及如何运行完全取决于Post方法的实现
- 该委托在何时何地以及如何运行完全取决于Post方法的实现
参考文章:
https://blog.csdn.net/starrycraft/article/details/113658608
https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext
https://blog.csdn.net/kalvin_y_liu/article/details/117787437?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase
【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext的更多相关文章
- 从执行上下文角度重新理解.NET(Core)的多线程编程[2]:同步上下文
一般情况下,我们可以将某项操作分发给任意线程来执行,但有的操作确实对于执行的线程是有要求的,最为典型的场景就是:GUI针对UI元素的操作必须在UI主线程中执行.将指定的操作分发给指定线程进行执行的需求 ...
- SynchronizationContext(同步上下文)综述
>>返回<C# 并发编程> 1. 概述 2. 同步上下文 的必要性 2.1. ISynchronizeInvoke 的诞生 2.2. SynchronizationContex ...
- 【C# TAP 异步编程】三、async\await的运作机理详解
[原创] 本文只是个人笔记,很多错误,欢迎指出. 环境:vs2022 .net6.0 C#10 参考:https://blog.csdn.net/brook_shi/article/details/ ...
- 执行上下文与同步上下文 | ExecutionContext 和 SynchronizationContext
原文连接:执行上下文与同步上下文 - .NET 并行编程 (microsoft.com) 执行上下文与同步上下文 斯蒂芬 6月15日, 2012 最近,我被问了几次关于 ExecutionContex ...
- 【C# TAP 异步编程】一 、async 修饰符(标记)
async的作用: 1.async是一个标记,告诉编译器这是一个异步方法. 2.编译器会根据这个标志生成一个异步状态机. 3.编译器将原异步方法中的代码清空,写入状态机的配置,原先异步方法中的代码被封 ...
- 深入理解JS异步编程四(HTML5 Web Worker)
>Web Workers 是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面. 一:如何使用Worker We ...
- 【C#TAP 异步编程】构造函数 OOP
原文:异步 OOP 2:构造函数 (stephencleary.com) 异步构造带来了一个有趣的问题.能够在构造函数中使用会很有用,但这意味着构造函数必须返回一个表示将来将构造的值,而不是构造的值. ...
- 【C# TAP 异步编程】二 、await运算符已经可等待类型Awaitable
await的作用: 1.await是一个标记,告诉编译器生成一个等待器来等待可等待类型实例的运行结果. 2.一个await对应一个等待器 ,任务的等待器类型是TaskAwaiter/TaskAwait ...
- 【C#TAP 异步编程】异步接口 OOP
在我们深入研究"异步OOP"之前,让我们解决一个相当常见的问题:如何处理异步方法的继承?那么"异步接口"呢? 幸运的是,它确实可以很好地与继承(和接口)一起使用 ...
随机推荐
- C++ 基本类型的大小
C++的基本类型: char bool (unsigned) short (int) (unsigned) int (unsigned) long (int) (unsigned) long long ...
- (2)RabbitMQ架构设计与应用场景
1.什么是消息中间件? 消息是指应用间传输的数据.消息体包括文本字符串.Json.内嵌对象等.消息中间件是基于队列模型实现异步和同步传输数据的.作用:解耦,冗余(存储).扩展性.削峰.可恢复性.顺序保 ...
- 关于基于python2.7的unity自动化测试框架GAutomator测试环境的搭建(源码网盘下载地址:https://pan.baidu.com/s/1c2TXwtU)
关于基于python 2.7的unity自动化测试框架GAutomator测试环境的搭建 百度云盘链接(思维图学习资料):https://pan.baidu.com/s/1dFWExMD 准备工作(具 ...
- c#代码设计:子类和父类
哭辽,事情是这样的 我想写个产品类用来放点相机参数,想类似这种的使用方式:(时间关系不改了,产品=Zoo,animals=相机) Zoo Zooxx= new Zoo (); Zoo.Animals ...
- c#序列化感悟(重点讲讲二进制序列化)
序列化是一种通过将对象转化为字节流,从而达到储存对象,或者将对象传输到内存,数据库或文件的一个过程,主要用途是保存对象的状态(包括对象的数据),方便对对象的重建,反之读取就是反序列化. 三种序列化类型 ...
- Activity 不只有跳转。功能篇(一)
Activity生命周期 1:activity四种启动方式 standard,SingleTask,SingleTop,SingleInstance standard:是系统默认的,每次启动该acti ...
- [USACO18DEC]Sort It Out P
初看本题毫无思路,只能从特殊的 \(K = 1\) 出发. 但是直接考虑构造一组字典序最小的方案还是不好构造,可以考虑先手玩一下样例.通过自己手玩的样例可以发现,貌似没有被选出来的数在原排列中都是递增 ...
- Tomcat临时目录及java.io.tmpdir对应的目录
最近客户现场的技术支持接连反馈了一个问题:导入数据的时候,上传的excel会在服务器上生成一个临时文件,而这个临时文件都在 tomcat 的安装目录下,如果上传次数比较多的话,就会导致tomcat安 ...
- Redis常用数据类型以及操作
Redis常用数据类型以及操作 目录 Redis常用数据类型以及操作 一.String数据类型 1. SET/GET/APPEND/STRLEN 2. INCR/DECR/INCRBY/DECRBY ...
- 【论文总结】Zero-Shot Semantic Segmentation
论文地址:https://arxiv.org/abs/1906.00817 代码:https://github.com/valeoai/ZS3 一.内容 Step 0:首先使用数据集(完全不包含 Un ...