自己写一个异步webapi,占了一个坑

照着前面文章 http://huqingyu.com/blog/2019/08/1511.htm,自己写了一个异步调用,改了路由,结果总是返回null

然后访问/api/cc/async,就是返回null,实际上它去了Get()方法…
访问/cc/async就正确了,嘿,被路由坑了

ASP.NET sync over async(异步中同步,什么鬼?)

src:https://www.cnblogs.com/xishuai/p/asp-net-sync-over-async.html

async/await 是我们在 ASP.NET 应用程序中,写异步代码最常用的两个关键字,使用它俩,我们不需要考虑太多背后的东西,比如异步的原理等等,如果你的 ASP.NET 应用程序是异步到底的,包含数据库访问异步、网络访问异步、服务调用异步等等,那么恭喜你,你的应用程序是没问题的,但有一种情况是,你的应用程序代码比较老,是同步的,但现在你需要调用异步代码,这该怎么办呢?有人可能会说,很简单啊,不是有个 .Result 吗?但事实真的就这么简单吗?我们来探究下。

首先,放出几篇经典文章:

上面文章的内容,我们后面会说。光看不练假把式,所以,如果真正要体会 sync over async,我们还需要自己动手进行测试:

  • 1. 异步调用使用 .Result,同步调用使用 .Result
  • 2. 异步调用使用 await,同步调用使用 Task.Run
  • 3. 异步调用使用 await,同步调用使用 .Result
  • 4. 异步调用使用 Task.Run,同步调用使用 .Result
  • 5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result
  • 6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result
  • 7. 异步调用使用 await,异步调用使用 await
  • 8. 测试总结

先说明一下,在测试代码中,异步调用使用的是 HttpClient.GetAsync 方法,并且测试请求执行两次,关于具体的分析,后面再进行说明。

1. 异步调用使用 .Result,同步调用使用 .Result

测试代码:

 

输出结果:


 

简单总结:同步代码中调用异步,上面的测试代码应该是我们最常写的,为什么没有出现线程阻塞,页面卡死的情况呢?而且代码中调用了 GetAsync,为什么请求线程只有一个?后面再说,我们接着测试。

2. 异步调用使用 await,同步调用使用 Task.Run

测试代码:

 

输出结果:

简单总结:根据上面的输出结果,我们发现,在一个请求过程中,总共会出现三个线程,一个是开始的请求线程,接着是 Task.Run 创建的一个线程,然后是异步方法中 await 等待的执行线程,需要注意的是,ManagedThreadId1 和 ManagedThreadId4 始终是一样的。

3. 异步调用使用 await,同步调用使用 .Result

测试代码:

 

输出结果:


 

简单总结:首先,页面是卡死状态,ManagedThreadId3 并没有输出,也就是执行到 await client.GetAsync 的时候,线程就阻塞了。

4. 异步调用使用 Task.Run,同步调用使用 .Result

测试代码:

 

输出结果:


 

简单总结:和第三种情况一样,页面也是卡死状态,但不同的是,ManagedThreadId3 是输出的,测试它的主要目的是和第三种情况形成对比,以便了解 HttpClient.GetAsync 中到底是什么鬼?

5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result

测试代码:

 

输出结果:


 

简单总结:和上面两种情况一样,页面也是卡死状态,它的效果和第三种完全一样,ManagedThreadId3 都没有输出的。

6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result

测试代码:

输出结果:


输出结果:

 

简单总结:和第五种情况形成对比,仅仅只是把 ConfigureAwait 参数设置为 false,结果却完全不同。

7. 异步调用使用 await,异步调用使用 await

测试代码:

 

简单总结:注意这是异步的写法,调用和被调用方法都是异步的,从输出的结果中,我们就会发现,这种情况和上面的六种情况,有一个最明显的区别就是,请求线程和结束线程不是同一个,说明什么呢?线程是异步等待的。

8. 测试总结

先梳理一下测试结果:

  1. 异步调用使用 .Result,同步调用使用 .Result:通过,始终一个线程。
  2. 异步调用使用 await,同步调用使用 Task.Run:通过,三个线程,请求开始和结束为相同线程。
  3. 异步调用使用 await,同步调用使用 .Result:卡死,线程阻塞。
  4. 异步调用使用 Task.Run,同步调用使用 .Result:卡死,线程阻塞。
  5. 异步调用使用 await .ConfigureAwait(true),同步调用使用 .Result:卡死,线程阻塞。
  6. 异步调用使用 await .ConfigureAwait(false),同步调用使用 .Result:通过,两个线程,await 执行为单独一个线程。
  7. 异步调用使用 await,异步调用使用 await:通过,两个线程,请求开始和结束为不同线程。

上面这么多的测试情况,看起来可能有些晕,我们先从最简单的第二种情况开始分析下,首先,页面是同步方法,请求线程可以看作是一个主线程 1,然后通过 Task.Run 创建线程 2,让它去执行 Test2 方法,需要注意的是,这时候主线程 1 并不会往下执行(从输出结果可以看出),它会等待线程 2 执行,主要是等待线程 2 执行返回结果,在 Test2 方法中,一切是异步方法,await client.GetAsync 会创建又一个线程 3 去执行,并且线程 2 等待它返回结果,然后最终回到线程 1 上,在整个过程中,虽然有三个线程,但这三个线程并不是同时工作的,而是一个执行之后等待另一个执行的结果,所以整个执行过程还是同步的。

第三种和第二种情况的不同就是,异步调用由 Task.Run 改成了 .Result,然后就造成了页面卡死,在 Don’t Block on Async Code 这篇文章中,就是详细说明的这种情况,为什么会卡死呢?其实你从同样卡死的第四种情况和第五种情况中,可以发现一些线索,ConfigureAwait 的说明是:试图继续回夺取的原始上下文,则为 true;否则为 false。什么意思呢?就是它可以变身为请求线程,最能体现出这一点的是,如果设置为 true,那么在这个线程中,就可以访问 HttpContext.Current,那为什么在同步调用中,设置为 true 就造成页面卡死呢?我们分析一下,页面是同步方法,请求线程可以看作是一个主线程 1,然后调用 Test3 异步方法,这时候主线程 1,会在这里等待异步的执行结果,在 Test3 方法中创建一个线程 2,因为把 ConfigureAwait 设置为了 true,那么线程 2 就想把自己变身成为请求线程(谋权篡位),也就是线程 1,但是人家线程 1 现在正在门口等它呢?线程 2 却想占有线程 1 的地位,很显然,这是不成功的,那什么情况下可以谋权篡位成功呢?就是线程 1 不在,也就是线程 1 回到线程池中了,这就是异步等待的效果,也是它的威力。

针对第三种情况,简单画了一个示意图:

在第五种情况中,因为把 ConfigureAwait 设置为 false,线程 2 不想谋权篡位了,它只想老老实实的做事,把执行结果返回给请求线程 1,那么整个请求执行过程就是顺利的。

同步调用异步测试中,还剩一个第一种情况,它和其他情况不同的是,没有异步方法,只是使用的是 .Result,那为什么它是通过的?并且线程始终是一个呢?首先,页面请求开始,创建一个请求线程 1,因为 Test 方法并不是异步方法,所以还是线程 1 去执行它,执行到了 client.GetAsync 这一步,因为没有使用 await,所以并不会创建一个线程去执行它,并且最终的是,虽然 GetAsync 是异步方法,但再其实现代码中,设置了 ConfigureAwait(false):

所以,整个过程应该是这样的,在测试代码中始终是一个请求线程在执行,并且在 client.GetAsync 的执行中,会创建另外一个线程 2 去执行,然后线程 1 等待线程 2 的执行结果,因为 GetAsync 的实现并不在测试代码中,所以表现出来就是一个线程在执行,虽然是异步方法,但它和同步方法一样,为什么?因为线程始终在等待另一个线程的执行结果,也就是说,在某一时刻,始终是一个线程在执行,其余线程都在等待。

sync over async(异步中同步)是否可行?通过上面的测试结果可以得出是可行的,但要注意一些写法问题:

  • 异步调用使用 .Result,而不能出现 await。
  • 不能出现 ConfigureAwait(true)。
  • 可以使用 Task.Run,但仅限于不返回结果的执行线程。

当然最好的方式是异步到底

freemarker tutorials

https://github.com/freemarker/freemarker-tutorials

00-running-a-server-locally

**Libraries Used**

* [FreeMarker 2.3.23](http://freemarker.org/docs/)
* [Spring MVC 4.2](https://spring.io/blog/2015/07/31/spring-framework-4-2-goes-ga)
* Javax Servlet API 3.1.0

## Step 1: Download or clone the FreeMarker Tutorials project

[Download and extract the FreeMarker Tutorials Github project](https://github.com/freemarker/freemarker-tutorials/archive/master.zip) or clone it using git (git clone https://github.com/freemarker/freemarker-tutorials.git)

## Step 2: Compile the Hello World project

Open up a console window (Command prompt for Windows users or Terminal for OS X users) and navigate to the tutorials/01-hello-world directory. Run mvn compile war:inplace.

bsh
mvn compile war:inplace

(If you don’t have Maven, follow the installation instructions here: [Installing Java and Maven](../00-installing-java-and-maven/))

This will download the dependencies and compile the Java files.

## Step 3: Point Tomcat at your webapp directory

1. In the [previous tutorial](../00-running-a-server-locally) we set up a Tomcat server. Make sure your server is still running!

* Windows users: Open up Command Prompt, type catalina start and press enter
* OS X users: Open up Terminal, type catalina start and press enter

2. Navigate to your Tomcat installation folder, and find the conf/Catalina/localhost directory and create a context file called **hello-world.xml**.

* Windows users: If you followed the previous tutorial’s instructions, Windows users can navigate directly to the folder by copying and pasting %CATALINA_HOME%\conf\Catalina\localhost into a Windows explorer window.

![Windows 7 XML file](images/win7-catalina-home.png)

* OS X users: In Terminal you can do this (Replace **8.0.26** with your version of Tomcat):

bsh
cd /usr/local/Cellar/tomcat/8.0.26/libexec/conf/Catalina/localhost
vi hello-world.xml

在${tomcat_home}/conf/Catalina/localhost新建一个文件hello-world.xml

3. Inside hello-world.xml, add the following (Replace **PATH_TO_FREEMARKER_TUTORIALS** with
wherever you have the FreeMarker tutorials project.):

xml
<?xml version="1.0" encoding="utf-8"?>
<Context docBase="PATH_TO_FREEMARKER_TUTORIALS/01-hello-world/src/main/webapp"
path="" reloadable="true" />

以上是新建文件hello-world.xml的内容,path自己替换一下

4. You should now be able to access the FreeMarker Hello World webapp at [http://localhost:8080/hello-world/](http://localhost:8080/hello-world/).

 

java maven checkstyle失败

官网: https://maven.apache.org/plugins/maven-checkstyle-plugin/index.html

checkstyle问题多多,需要一一解决,先来看看可能出现的问题:
Execution verify of goal org.apache.maven.plugins:maven-checkstyle-plugin:2.16 …
Could not find resource ‘etc/config/checkstyle.xml’
Could not find resource ‘etc/config/checkstyle-suppressions.xml’

它说没找到这两个文件,那么贴2个上去,在项目下建立这3个文件:

checkstyle.xml
etc/config/checkstyle-verify.xml

checkstyle-suppressions.xml

 

特别说明:官网上这个是错的 https://maven.apache.org/plugins/maven-checkstyle-plugin/examples/suppressions-filter.html
里面的https://checkstyle.org/dtds/suppressions_1_0.dtd早就没了

然后在pom.xml的<build>里面加上:注意是build

好,这样能解决大部分问题了。

iTextPDF7 Core for Java

download:
https://itextpdf.com/en/resources/api-documentation/itext-7-java

document (demo):
https://itextpdf.com/en/resources/examples/itext-7/itext-7-jump-start-tutorial-chapter-1#1725-c01e03_quickbrownfox.java

 

emcc编译选项

source: https://emscripten.org/docs/compiling/Building-Projects.html

To see a list of all available ports
emcc
 –show-ports

一、使用SDL_image

参考【1】用这个可以获得image宽、高,返回image data
Gets preloaded image data and the size of the image.

二、使用SDL_net
SDL_net has also been added to ports, use it with

三、编译优化
In order to properly optimize code, it is usually best to use the same optimization flags and other compiler options when compiling source to object code, and object code to JavaScript (or HTML).

 

参考:
1. emscripten.h
https://emscripten.org/docs/api_reference/emscripten.h.html#c.emscripten_get_preloaded_image_data

[Emscripten+WebGL] jpg/jpeg image viewer

source code: https://github.com/svoisen/wasm-imageviewer.git

Makefile:

CFLAGS=-Werror -std=c++11 -O1 -g0
EMSCRIPTEN_FLAGS=-s FULL_ES3=1 \
-s USE_SDL=2 \
-s WASM=1 \
-s GL_DEBUG=1 \
-s TOTAL_MEMORY=134217728 \
-s EXTRA_EXPORTED_RUNTIME_METHODS=”[‘ccall’, ‘cwrap’]” \
-s EXPORTED_FUNCTIONS='[“_initializeOpenGL”, “_render”, “_loadJPEGImage”]’

由于这个demo问题比较多,所以打开了debug,然后优化也只用O1,尚不清楚FULL_ES3=1是不是必要的,有待验证

renderer.cpp改动:

1)出错信息打印:

2)编译之后的renderer.js
“webgl”改成”webgl2″!这个重要!

因为之前编译后运行起来提示:
ERROR: unsupported shader version
搞不懂为什么,寻思可能cpp中的shader头写的不对?

参考【1】说:#version 300 es必须顶格写!所以

是错误的!
正确是

<scritpt>后面没有换行!

把cpp各种修改,都没啥效果,看了参考【2】,也是这么写的,没问题。

后来想,既然是初始化出问题,那把这个c代码的初始化改写成简单的html+js,看看怎么样。

html内容:

1.js内容:

关键就在于:
webgl1是 canvas.getContext(“webgl“);
webgl2是canvas.getContext(“webgl2“);

所以,把编译好的renderer.js里面”webgl”改成”webgl2″,就ok了!这个bug对初学者来说有点难度。这里记一个解决的过程。

 

 


第二天接着玩,换了台机器,发现照上面做了还是不行
自己做个最简单的canvas然后canvas.getContext(‘webgl2’)都不行,返回的是null
canvas.getContext(‘webgl’)是可以的,然后换Firefox是可以的,所以判断是chrome的flag哪里不对。于是一个个是过去,发现这个:

它有四个选项:
Default(默认)
OpenGL
D3D11
D3D9

改成OpenGL就好了。

 

再一次试了下编译,发现这样的编译选项,编译后的js就可以有getContext(‘webgl2’),不用再改js。

emcc renderer.cpp -Werror -std=c++11 -O0 -g0 -s USE_SDL=2 -s USE_WEBGL2=1 -s FULL_ES3=1 -s GL_DEBUG=1 -s WASM=1 -s TOTAL_MEMORY=134217728 -s EXPORTED_FUNCTIONS=”[‘_initializeOpenGL’, ‘_render’, ‘_loadJPEGImage’]” -s EXTRA_EXPORTED_RUNTIME_METHODS=”[‘ccall’, ‘cwrap’]” –post-js renderer_post.js -o renderer.js

然而最后运行的结果却是还不行,调试的时候看到:
var ctx =
(webGLContextAttributes.majorVersion > 1) ? canvas.getContext(“webgl2”, webGLContextAttributes) :
(canvas.getContext(“webgl”, webGLContextAttributes) || canvas.getContext(“experimental-webgl”, webGLContextAttributes));

这个majorVersion总是1,看了下参考2,它的c代码用的是egl,不一样。感觉已经不是编译选项的问题了,要改代码。又去问度娘,问谷歌,最后在renderer.cpp上加一句:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
再编译就行了!默认值是2,然后js里面会再减1,所以webGLContextAttributes.majorVersion就是1,然后就悲剧了。

 

 

 

参考:
【1】https://segmentfault.com/a/1190000012002453
【2】https://github.com/danginsburg/opengles3-book.git

tiff2bmpsw using libtiff wasm

git source: https://github.com/horo-t/tiff2bmpsw.git

WebAssembly实现tiff转bmp(使用libtiff库)
1) download source:
http://download.osgeo.org/libtiff/tiff-4.0.9.tar.gz
2) 解压、patch
tar xvfz tiff-4.0.9.tar.gz
cd tiff-4.0.9
patch -u libtiff/tiff.h < ../tiff_h_int_size.patch
3)编译libtiff
emconfigure
emmake make
4)编译tiff2bmp
cd..
emcc tiff2bmpsw.c \
./tiff-4.0.9/libtiff/.libs/libtiff.a \
-I./tiff-4.0.9/libtiff \
-s EXPORTED_FUNCTIONS=”[‘_tiff_to_bmp’]” \
-s EXTRA_EXPORTED_RUNTIME_METHODS=”[‘ccall’, ‘cwrap’]” \
–pre-js LICENSE \
–pre-js tiff2bmpsw_pre.js \
–js-library tiff2bmpsw_jslib.js \
–post-js tiff2bmpsw_post.js \
-o tiff2bmpsw.js

注意加粗部分为改过的,红色部分为新加上去的不然运行报ccall没导出的错误

编完后把tiff2bmpsw.html、tiff2bmpsw.js、tiff2bmpsw.wasm放在web服务器上,就可以玩了