tnblog
首页
视频
资源
登录

.net core Task的学习理解(上)

14928人阅读 2021/8/18 12:06 总访问:3467241 评论:2 收藏:1 手机
分类: .net后台框架

.netcore

.net core Task的学习理解(上)

Thread 线程的生命周期


计算机系统在某一个时刻,当只有一个CPU工作时,它只执行一个进程中的一个线程,但是用户在使用计算机时回打开多个进行,这样就涉及到了线程的不同状态,一个进程可以创建多个线程。


当一个cpu从一个线程调用另一个线程的时候,需要将Thread1线程的状态保存到PCB1中,然后加载PCB2中Thread2的状态并调用Thread2的线程,当时间片用尽的时候再保存状态到PCB2中。…

当线程有很多的时候,CPU会不停的切换上下文,此时就会非常消耗CPU性能。所以线程池就出现了。

ThreadPool 线程池


线程池里面通过设置好线程的数量,可以将空闲的线程进行回收利用。可以通过preferLocal:false把任务放到线程池的(global queue)全局队列中,利用空闲的线程执行该任务;也可以通过preferLocal:true将放入线程中本地队列进行执行。


线程池内部有两种队列,global queue和每个worker thread绑定的local queue。
Work item 的存储类型是Object,实际被 worker thread 执行的时候会判断其类型执行对应的入口方法。
work item 类型可能是Task也可能是IThreadPoolWorkItem。


当全局任务队列里面的任务也没有的时候,算法将会把其他有任务的线程中的任务,分配给这些空闲的线程去执行。其目的是每个线程都有事干。

ThreadPoolTaskScheduler


我们先举个例子。Task.Run是我们常用的调用方式,这里我们通过委托的方式去获取它默认的调度器与委托,可以看到它默认的调度器是ThreadPoolTaskScheduler。

  1. static void Main(string[] args)
  2. {
  3. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
  4. Task.Run(() => {
  5. Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
  6. Console.WriteLine(TaskScheduler.Current.GetType());
  7. });
  8. Console.ReadLine();
  9. }


ThreadPoolTaskScheduler是在ThreadPool之上的一层,它确定一般模式下会将该任务(Task)对象分配给线程池(ThreadPool)的全局队列中,然后将其分配到哪个线程(Thread)的本地队列中进行执行。如果设置为LongRunning模式将不会把任务分配到线程池中,执行由下图所示。

Task并不是线程,而是一个可以封装执行过程的一个对象实体。比如上一个案例中我们就是封装了一个委托。

线程与协程

并行计算的协程


可以多个任务通过不同的线程执行进行计算。
比如下列例子:通过task1与task2不同的线程计算最后求积。

  1. static async Task Main(string[] args)
  2. {
  3. Task<int> task1 = Task.Run<int>(()=> { Console.WriteLine($"task1 finish [Thread]:{Thread.CurrentThread.ManagedThreadId}"); return 1; });
  4. Task<int> task2 = Task.Run<int>(()=> { Console.WriteLine($"task2 finish [Thread]:{Thread.CurrentThread.ManagedThreadId}"); return 2; });
  5. Text((await task1) * (await task2));
  6. Console.ReadLine();
  7. }
  8. static void Text(int i)
  9. {
  10. Console.WriteLine(i);
  11. }

  1. # 这样也可以
  2. await Task.WhenAll(task1, task2);


在实际运用场景中我们可以计算工资啊,账单结算啊….

如果不支持Main的异步,需要在配置文件中添加这几行。

  1. <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
  2. <LangVersion>7.1</LangVersion>
  3. </PropertyGroup>

协程与异步IO


Task1发起请求然后自己的工作完成了,接着由IO等待线程接力,获取完所有IO后,由Task2进行处理。


我们通过请求百度之前,发现是由线程1处理该过程,IO获取完毕后是由线程8进行处理的。

  1. static async Task Main(string[] args)
  2. {
  3. await downloadtask();
  4. Console.ReadLine();
  5. }
  6. static async Task downloadtask()
  7. {
  8. using (var client = new HttpClient())
  9. {
  10. Console.WriteLine($"Before IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  11. Console.WriteLine($"Before IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  12. Console.WriteLine($"Before IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  13. var result = await client.GetStringAsync("https://www.baidu.com/");
  14. Console.WriteLine($"After IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  15. Console.WriteLine($"After IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  16. Console.WriteLine($"After IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  17. Console.WriteLine($"After IO [Thread]:{Thread.CurrentThread.ManagedThreadId}");
  18. }
  19. }

await与线程的切换没有半点关系。

本地队列与全局队列


我们通过编写如下代码,将任务放到本地队列中,并且执行5次,看看执行结果。

  1. for (int i = 0; i < 5; i++)
  2. {
  3. int b = i;
  4. Task.Run(() => Console.WriteLine($"Task{b} ThreadId:{Thread.CurrentThread.ManagedThreadId}"))
  5. .ContinueWith(_ => Console.WriteLine($"continuationAction{b} ThreadId:{Thread.CurrentThread.ManagedThreadId}"));
  6. Task.Delay(2000);
  7. }


我发现Task.Run与ContinueWith的线程Id是同一个线程,在运行Task4的时候由于线程7空闲了,直接将Task4的任务执行了。
接着我们修改部分代码看看结果。

  1. Task.Run(() => Console.WriteLine($"Task1 ThreadId:{Thread.CurrentThread.ManagedThreadId}"))
  2. .ContinueWith(_ => Console.WriteLine($"continuationAction1 ThreadId:{Thread.CurrentThread.ManagedThreadId}"));
  3. Task.Run(() => Console.WriteLine($"Task2 ThreadId:{Thread.CurrentThread.ManagedThreadId}"))
  4. .ContinueWith(_ => Console.WriteLine($"continuationAction2 ThreadId:{Thread.CurrentThread.ManagedThreadId}"), TaskContinuationOptions.RunContinuationsAsynchronously);
  5. Console.ReadLine();


很奇怪为什么不一样?这是因为Task1刚执行完,看到Task2还在执行,线程4就没事做了就去task2后面的continuationAction2任务做了。线程6发现自己没事做然后就去task1里面的continuationAction1拿过来做了。

自定义TaskScheduler


并不是所有的Task运行时都会到线程池(ThreadPool)里面去,这取决于它的TaskScheduler是哪一个;要想指定哪一个TaskScheduler需要通过TaskFactory来进行指定。下面我们通过编写自定义的TaskSchedule来进行测试。

  1. class CustomTaskScheduler : TaskScheduler
  2. {
  3. /// <summary>
  4. /// 定义一个线程安全的Task集合
  5. /// </summary>
  6. private BlockingCollection<Task> _queue = new BlockingCollection<Task>();
  7. public CustomTaskScheduler() =>
  8. new Thread(() =>
  9. {
  10. while (true)
  11. {
  12. // 从队列中取出一个
  13. var task = _queue.Take();
  14. // 进行执行
  15. TryExecuteTask(task);
  16. Console.WriteLine($"task {task.Id} Executed");
  17. }
  18. })
  19. {
  20. // 定义为后台
  21. IsBackground = true
  22. }.Start();
  23. /// <summary>
  24. /// 获取所有Task
  25. /// </summary>
  26. /// <returns></returns>
  27. protected override IEnumerable<Task> GetScheduledTasks() => _queue.ToArray();
  28. /// <summary>
  29. /// 添加Task
  30. /// </summary>
  31. /// <param name="task"></param>
  32. protected override void QueueTask(Task task) => _queue.Add(task);
  33. /// <summary>
  34. /// 确定提供的Task是否可以同步执行
  35. /// </summary>
  36. /// <param name="task"></param>
  37. /// <param name="taskWasPreviouslyQueued"></param>
  38. /// <returns></returns>
  39. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false;
  40. }
  1. static void Main(string[] args)
  2. {
  3. var taskFactory = new TaskFactory(new CustomTaskScheduler());
  4. taskFactory.StartNew(() => Console.WriteLine($"task {Task.CurrentId}" + $"threadId:{Thread.CurrentThread.ManagedThreadId}"));
  5. taskFactory.StartNew(() => Console.WriteLine($"task {Task.CurrentId}" + $"threadId:{Thread.CurrentThread.ManagedThreadId}"));
  6. Console.ReadLine();
  7. }

有限元状态机(finite-state machine)


有限元状态机主要解决下列几个问题:
1.原来的线程去了哪里?
2.await表达式的返回值是怎么回事?
3.await之前的变量是如何被await之后的代码获取的?
4.await之后的任务在哪执行?
5.为什么有时候线程没有发生切换?


FooAsync里面的await Task.Delay(100);会编译成如下代码。而FooStateMachine就是状态机。(3)关于它的变量被编译成字段。并且初始化状态机状态为-1,接着开始执行状态机里面的内容。而MoveNext方法里面的内容就是执行的主要部分,(4)其中需要执行的代码也在该方法里面。


接着我们执行MoveNext里面的内容。一开始状态是-1所以我们执行_state!=0这个判断下面的内容,然后通过需要执行的任务(Task)中获取GetAwaiter去进行判断是否执行完成。
如果执行完成将直接到GetResult方法获取结果然后返回该结果,执行结束。
如果taskAwaiter没有执行完成,状态将会改为0,并且通过异步的方式等待完成,封装并且将再次回调MoveNext方法(想在什么地方回调就在什么地方回调)。是AwaitUnsafeOnCompleted的主要工作。
当第二次进行执行的时候会执行else下面的内容,(2)通过taskAwaiter去获取结果,不管返回值需不需要结果都会去调用GetResult方法。
最后将通过SetResult放上结果值进行返回。


整个执行流程图

Task内部结构


其核心的接口有两个INotifyCompletionICriticalNotifyCompletion。其中OnCompleted方法用来传递执行上下文,当实现ICriticalNotifyCompletion接口时就会去调用UnsafeOnCompleted这个方法。

自定义Awaiter


可以按照该结构去进行自定义一个Awaiter

  1. public class CustomAwaiter<T> : ICriticalNotifyCompletion
  2. {
  3. private Action _continuation;
  4. private T _result;
  5. private Exception _exception;
  6. public bool IsCompleted { get; private set; }
  7. public void OnCompleted(Action continuation)
  8. {
  9. Console.WriteLine("CustomAwaiter.OnCompleted"+$"continuation:{continuation.Target.GetType().Name}.{continuation.Method}");
  10. _continuation = continuation;
  11. }
  12. public void UnsafeOnCompleted(Action continuation)
  13. {
  14. Console.WriteLine("CustomAwaiter.UnsafeOnCompleted" + $"continuation:{continuation.Target.GetType().Name}.{continuation.Method}");
  15. _continuation = continuation;
  16. }
  17. public T GetResult()
  18. {
  19. Console.WriteLine("CustomAwaiter.GetResult");
  20. if (_exception != null) throw _exception;
  21. return _result;
  22. }
  23. public void SetResult(T result)
  24. {
  25. Console.WriteLine("CustomAwaiter.SetResult");
  26. IsCompleted = true;
  27. _result = result;
  28. _continuation.Invoke();
  29. }
  30. public void SetException(Exception exception)
  31. {
  32. Console.WriteLine("CustomAwaiter.SetException");
  33. IsCompleted = true;
  34. _exception = exception;
  35. _continuation.Invoke();
  36. }
  37. }
  1. public class CustomAwaitable<T>
  2. {
  3. private CustomAwaiter<T> _awaiter = new CustomAwaiter<T>();
  4. public CustomAwaiter<T> GetAwaiter()
  5. {
  6. Console.WriteLine("CustomAwaitable.GetAwaiter");
  7. return _awaiter;
  8. }
  9. }


测试执行代码

  1. static async Task Main(string[] args)
  2. {
  3. var foo = await Foo();
  4. Console.WriteLine(foo);
  5. }
  6. static CustomAwaitable<string> Foo()
  7. {
  8. Console.WriteLine("Foo");
  9. var awaitable = new CustomAwaitable<string>();
  10. var awaiter = awaitable.GetAwaiter();
  11. new Thread(() =>
  12. {
  13. Thread.Sleep(100);
  14. awaiter.SetResult("Hello World!");
  15. })
  16. {
  17. IsBackground = false
  18. }.Start();
  19. return awaitable;
  20. }


如果运气好,执行如下


运气不好,就会报错。报错原因是因为SetResultUnsafeOnCompleted回调未完成的之前执行了,_continuation字段的委托并没有MoveNext方法。(大佬建议不使用)


我做了一些修改可能会导致性能不太佳,但勉勉强强能避开这个问题。

  1. public class CustomAwaiter<T> : ICriticalNotifyCompletion
  2. {
  3. private Action _continuation;
  4. private T _result;
  5. private Exception _exception;
  6. AutoResetEvent autoEvent = new AutoResetEvent(false);
  7. public bool IsCompleted { get; private set; }
  8. public void OnCompleted(Action continuation)
  9. {
  10. Console.WriteLine("CustomAwaiter.OnCompleted"+$"continuation:{continuation.Target.GetType().Name}.{continuation.Method}");
  11. _continuation = continuation;
  12. }
  13. public void UnsafeOnCompleted(Action continuation)
  14. {
  15. Console.WriteLine("CustomAwaiter.UnsafeOnCompleted" + $"continuation:{continuation.Target.GetType().Name}.{continuation.Method} {Thread.CurrentThread.ManagedThreadId}");
  16. _continuation = continuation;
  17. autoEvent.Set();
  18. }
  19. public T GetResult()
  20. {
  21. Console.WriteLine("CustomAwaiter.GetResult");
  22. if (_exception != null) throw _exception;
  23. return _result;
  24. }
  25. public void SetResult(T result)
  26. {
  27. Console.WriteLine($"CustomAwaiter.SetResult {Thread.CurrentThread.ManagedThreadId}");
  28. IsCompleted = true;
  29. _result = result;
  30. if (_continuation == null)
  31. {
  32. autoEvent.WaitOne();
  33. }
  34. _continuation.Invoke();
  35. }
  36. public void SetException(Exception exception)
  37. {
  38. Console.WriteLine("CustomAwaiter.SetException");
  39. IsCompleted = true;
  40. _exception = exception;
  41. _continuation.Invoke();
  42. }
  43. }

当我们运行的程序可能会报错时,Task的异常信息里不会有GetResult的StackFrame,这里我们自己的程序是会报错的。
因为在Task的GetResult的方法里面会添加StackTraceHidden特性。

  1. static async Task Main(string[] args)
  2. {
  3. var foo = await Foo();
  4. Console.WriteLine(foo);
  5. }
  6. static CustomAwaitable<string> Foo()
  7. {
  8. Console.WriteLine("Foo");
  9. var awaitable = new CustomAwaitable<string>();
  10. var awaiter = awaitable.GetAwaiter();
  11. new Thread(() =>
  12. {
  13. try
  14. {
  15. Thread.Sleep(100);
  16. Console.WriteLine($"child Thread {Thread.CurrentThread.ManagedThreadId}");
  17. throw new Exception("Hello Bug!");
  18. }
  19. catch (Exception ex)
  20. {
  21. awaiter.SetException(ex);
  22. }
  23. })
  24. {
  25. IsBackground = false
  26. }.Start();
  27. return awaitable;
  28. }

async的支持


虽然实现了await的方法但是并没有实现async的支持,所以这里async并不会认识我们定义的CustomAwaiter,相比async Task方法的编译结果,我们少了个AsyncTaskMethodBuilder

AsyncTaskMethodBuilder


关于AsyncMethodBuilder的结构如下图所示。


如果要我们去实现async的支持的话,需要我们去定义自己的AsyncMethodBuilder

  1. public struct CustomAsyncMethodBuilder<T>
  2. {
  3. private CustomAwaiter<T> _awaiter;
  4. private CustomAwaitable<T> _awaitable;
  5. /// <summary>
  6. /// 创建CustomAsyncMethodBuilder
  7. /// </summary>
  8. /// <returns></returns>
  9. public static CustomAsyncMethodBuilder<T> Create()
  10. {
  11. Console.WriteLine("CustomAsyncMethodBuilder.Create");
  12. var awaitable = new CustomAwaitable<T>();
  13. var builder = new CustomAsyncMethodBuilder<T>
  14. {
  15. _awaitable = awaitable,
  16. _awaiter = awaitable.GetAwaiter()
  17. };
  18. return builder;
  19. }
  20. /// <summary>
  21. /// CustomAsyncMethodBuilder 的 任务
  22. /// </summary>
  23. public CustomAwaitable<T> Task
  24. {
  25. get
  26. {
  27. Console.WriteLine("CustomAsyncMethodBuilder.Task");
  28. return _awaitable;
  29. }
  30. }
  31. public void SetException(Exception exception)
  32. {
  33. Console.WriteLine("CustomAsyncMethodBuilder.SetException");
  34. _awaiter.SetException(exception);
  35. }
  36. public void SetResult(T result)
  37. {
  38. Console.WriteLine("CustomAsyncMethodBuilder.SetResult");
  39. _awaiter.SetResult(result);
  40. }
  41. /// <summary>
  42. /// 调用OnCompleted方法
  43. /// </summary>
  44. /// <typeparam name="TAwaiter"></typeparam>
  45. /// <typeparam name="TStateMachine"></typeparam>
  46. /// <param name="awaiter"></param>
  47. /// <param name="stateMachine"></param>
  48. public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter,ref TStateMachine stateMachine)
  49. where TAwaiter : INotifyCompletion
  50. where TStateMachine : IAsyncStateMachine
  51. {
  52. Console.WriteLine("CustomAsyncMethodBuilder.AwaitOnCompleted");
  53. _awaiter.OnCompleted(stateMachine.MoveNext);
  54. }
  55. /// <summary>
  56. /// 调用AwaitUnsafeOnCompleted方法
  57. /// </summary>
  58. /// <typeparam name="TAwaiter"></typeparam>
  59. /// <typeparam name="TStateMachine"></typeparam>
  60. /// <param name="awaiter"></param>
  61. /// <param name="stateMachine"></param>
  62. /// 将类型或成员标识为安全关键型,并可由透明代码安全访问
  63. [SecuritySafeCritical]
  64. public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
  65. where TAwaiter : INotifyCompletion
  66. where TStateMachine : IAsyncStateMachine
  67. {
  68. Console.WriteLine("CustomAsyncMethodBuilder.AwaitUnsafeOnCompleted");
  69. // var iscompleted = bool.Parse(awaiter.GetType().GetProperty("IsCompleted").GetValue(awaiter).ToString());
  70. _awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
  71. }
  72. public void Start<TStateMachine>(ref TStateMachine stateMachine)
  73. where TStateMachine:IAsyncStateMachine
  74. {
  75. Console.WriteLine("CustomAsyncMethodBuilder.Start");
  76. stateMachine.MoveNext();
  77. }
  78. public void SetStateMachine(IAsyncStateMachine stateMachine)
  79. {
  80. Console.WriteLine("CustomAsyncMethodBuilder.SetStateMachine");
  81. }
  82. }


最后还需要在CustomAwaitable上添加自定义的CustomAsyncMethodBuilder特性

  1. [AsyncMethodBuilder(typeof(CustomAsyncMethodBuilder<>))]
  2. public class CustomAwaitable<T>
  3. {
  4. private CustomAwaiter<T> _awaiter = new CustomAwaiter<T>();
  5. public CustomAwaiter<T> GetAwaiter()
  6. {
  7. Console.WriteLine("CustomAwaitable.GetAwaiter");
  8. return _awaiter;
  9. }
  10. }


最后CustomAwaiter也做了一些修改

  1. public class CustomAwaiter<T> : ICriticalNotifyCompletion
  2. {
  3. private Action _continuation;
  4. private T _result;
  5. private Exception _exception;
  6. // AutoResetEvent autoEvent = new AutoResetEvent(false);
  7. public bool IsCompleted { get; private set; }
  8. public void OnCompleted(Action continuation)
  9. {
  10. Console.WriteLine("CustomAwaiter.OnCompleted"+$"continuation:{continuation.Target.GetType().Name}.{continuation.Method}");
  11. _continuation = continuation;
  12. }
  13. public void UnsafeOnCompleted(Action continuation)
  14. {
  15. Console.WriteLine("CustomAwaiter.UnsafeOnCompleted" + $"continuation:{continuation.Target.GetType().Name}.{continuation.Method} {Thread.CurrentThread.ManagedThreadId}");
  16. _continuation = continuation;
  17. _continuation.Invoke();
  18. // autoEvent.Set();
  19. }
  20. public T GetResult()
  21. {
  22. Console.WriteLine("CustomAwaiter.GetResult");
  23. if (_exception != null) throw _exception;
  24. return _result;
  25. }
  26. public void SetResult(T result)
  27. {
  28. Console.WriteLine($"CustomAwaiter.SetResult {Thread.CurrentThread.ManagedThreadId}");
  29. IsCompleted = true;
  30. _result = result;
  31. }
  32. public void SetException(Exception exception)
  33. {
  34. Console.WriteLine("CustomAwaiter.SetException");
  35. IsCompleted = true;
  36. _exception = exception;
  37. }
  38. }


接着我们测试一下

  1. static async Task Main(string[] args)
  2. {
  3. var boo = await Boo();
  4. Console.WriteLine(boo);
  5. Console.ReadLine();
  6. }
  7. static async CustomAwaitable<string> Boo()
  8. {
  9. await Task.Run(() => Console.WriteLine("Finish"));
  10. string str = "bob";
  11. return str;
  12. }


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

剑轩

2021/8/20 12:46:59

这......么.....长

尘叶心繁:@剑轩还有下集

2021/8/20 13:33:12 回复

.net core Task的学习理解(下)

.net core Task的学习理解(下)[TOC] Task.GetResult与死锁通过下面的代码,可能会造成死锁的情况。static async Task Main...

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

.net core 使用 Kestrel

Kestrel介绍 Kestrel是一个基于libuv的跨平台web服务器 在.net core项目中就可以不一定要发布在iis下面了Kestrel体验可以使...

net core中使用cookie

net core中可以使用传统的cookie也可以使用加密的cookieNET CORE中使用传统cookie设置:HttpContext.Response.Cookies.Appe...

net core项目结构简单分析

一:wwwrootwwwroot用于存放网站的静态资源,例如css,js,图片与相关的前端插件等lib主要是第三方的插件,例如微软默认引用...

net core使用EF之DB First

一.新建一个.net core的MVC项目新建好项目后,不能像以前一样直接在新建项中添加ef了,需要用命令在添加ef的依赖二.使用Nug...

.net core使用requestresponse下载文件下载excel等

使用request获取内容net core中request没有直接的索引方法,需要点里边的Query,或者formstringbase64=Request.Form[&quot;f...

iframe自适应高度与配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...

net core启动报错Unable to configure HTTPS endpoint. No server certificate was specified

这是因为net core2.1默认使用的https,如果使用Kestrel web服务器的话没有安装证书就会报这个错其实仔细看他的错误提示,其...

net core中使用url编码与解码操作

net core中暂时还没有以前asp.net与mvc中的server对象。获取url的编码与解码操作不能使用以前的server对象来获取。使用的是...
这一世以无限游戏为使命!
排名
2
文章
634
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术