(转) Twisted :第十九部分 改变之前的想法

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

简介

Twisted是一个正在进展的项目,它的开发者会定期添加新的特性并且扩展旧的特性.

随着Twisted 10.1.0发布,开发者向 Deferred 类添加了一个新的特性—— cancellation ——这正是我们今天要研究的.

异步编程将请求和响应解耦了,如此又带来一个新的可能性:在请求结果和返回结果之间,你可能决定不再需要这个结果了.考虑一下 :doc:`p14` 中的诗歌代理服务器.下面是这个如何工作的,至少对于诗歌的第一次请求:

  1. 一个对诗歌的请求来了.

  2. 这个代理联系实际服务器以得到这首诗

  3. 一旦这首诗完成,将其发送给原发出请求的代理

看起来非常完美,但是如果客户端在获得诗歌之前挂了怎么办?也许它们先前请求 Paradise Lost 的全部内容,随后它们决定实际想要的是 Kojo 的俳句.我们的代理将陷入下载前者,并且那个慢服务器会等好一会.最好的策略便是关闭连接,让慢服务器回去顺觉.

回忆一下 第十五部分,展示了同步程序控制流的概念.在那张图中我们可以看到函数调用自上而下,异常是自下而上.如果我们希望取消一个同步调用(这仅是假设),控制流的传递方向与函数调用的方向一致,都是从高层传向底层,如图38所示:

_static/p19_sync-cancel.png

图38:同步程序流,含假想取消操作

当然,在同步程序中这是不可能的,因为高层的代码在底层操作结束前没有恢复运行,自然也就没有什么可取消的.但是在异步程序中,高层代码在底层代码完成前具有控制权,至少具有在底层代码完成之前取消它的请求的可能性.

在Twisted程序中,底层请求被包含在一个 Deferred 对象中,你可以将其想象为一个外部异步操作的"句柄". deferred 中正常的信息流是向下的,从底层代码到高层代码,与同步程序中返回的信息流方向一致.从Twisted 10.1.0开始,高层代码可以反向发送信息 —— 它可以告诉底层代码它不再需要其结果了.如图39:

_static/p19_deferred-cancel.png

图39: deferred 中的信息流,包含取消


取消 Deferreds

让我们看一些例程,来了解下取消 deferreds 的实际工作原理.注:为了运行这些列子以及本部分中的其他代码,你需要安装Twisted 10.1.0或更高 版本. 考虑 deferred-cancel/defer-cancel-1.py:

from twisted.internet import deferdef callback(res):print 'callback got:', resd = defer.Deferred()
d.addCallback(callback)
d.cancel()
print 'done'

伴随着新的取消特性, Deferred 类获得一个名为 cancel 的新方法.上面代码创建了一个新的 deferred,添加了一个回调,这后取消了这个 deferred 而没有激发它.输出如下:

done
Unhandled error in Deferred:
Traceback (most recent call last):
Failure: twisted.internet.defer.CancelledError:

OK,取消一个 deferred 看起来像使错误回调链运行,常规的回调根本没有被调用.同样注意到这个错误是: twisted.internet.defer.CancelledError,一个意味着 deferred 被取消的个性化异常(但请继续阅读).让我们添加一个错误回调,如deferred-cancel/defer-cancel-2.py

from twisted.internet import deferdef callback(res):print 'callback got:', resdef errback(err):print 'errback got:', errd = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
print 'done'

得到以下输出:

errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]
done

所以我们可以'捕获'从 cancel 产生的错误回调,就像其他 deferred 错误一样.

OK,让我们试试激发 deferred 然后取消它,如 deferred-cancel/defer-cancel-3.py:

from twisted.internet import deferdef callback(res):print 'callback got:', resdef errback(err):print 'errback got:', errd = defer.Deferred()
d.addCallbacks(callback, errback)
d.callback('result')
d.cancel()
print 'done'

这里我们用常规 callback 方法激发 deferred,之后取消它.输出结果如下:

callback got: result
done

我们的回调被调用(正如我们所预期的)之后程序正常结束,就像 cancel 根本没有被调用.所以取消一个 deferred 好像根本没有效果如果它已经被激发(但请继续阅读!).

如果我们在取消 deferred 之后激发它会怎样?参看 deferred-cancel/defer-cancel-4.py:

from twisted.internet import deferdef callback(res):print 'callback got:', resdef errback(err):print 'errback got:', errd = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
d.callback('result')
print 'done'

这种情况的输出如下:

errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]
done

有意思!与第二个例子的输出一样,当时没有激发 deferred.所以如果 deferred 被取消了,再激发它没有效果.但是为什么 d.callback('result') 没有产生错误,考虑到不能激发 deferred 大于一次,错误回调链为何没有运行?

再次考虑 figure39.用结果或失败激发一个 deferred 是底层代码的工作,然而取消 deferred 是高层代码的行为.激发deferred 意味着"这是你的结果",然而取消 deferred 意味着"我不再想要这个结果了".同时记住 cancel 是一个新特性,所以大部分现有的Twisted代码并没有处理取消的操作.但是Twisted的开发者使我们取消 deferred 的想法变得有可能,甚至包括那些在Twisted 10.1.0之前写的代码.

为了实现以上想法, cancel 方法实际上做两件事:

  1. 告诉 Deferred 对象本身你不想要那个结果,如果它还没有返回(如, deferred 没有被激发),这样忽略任何回调或错误回调的后续调用.

  2. 同时,可选地,告诉正在产生结果的底层代码需要采取何种步骤来取消操作.

由于旧版本的Twisted代码会上前去激发任何已经被取消的 deferred, step#1确保我们的程序不会垮掉如果我们取消一个旧有库中的 deferred.

这意味着我们可以随心所欲地取消一个 deferred,同时可以确定不会得到结果如果它还没有到来(甚至那些 将要 到来的).但是取消 deferred 可能并没有取消异步操作.终止一个异步操作需要一个上下文的具体行动.你可能需要关闭网络连接,回滚数据库事务,结束子进程,等等.由于 deferred 仅仅是一般目的的回调组织者,它怎么知道具体要做什么当你取消它时?或者,换种说法,它怎样将 cancel 请求传递给首先已经创建和返回了 deferred 的底层代码? 和我一起说:

I know, with a callback!


本质上取消 Deferreds

好吧,首先看一下 deferred-cancel/defer-cancel-5.py:

from twisted.internet import deferdef canceller(d):print "I need to cancel this deferred:", ddef callback(res):print 'callback got:', resdef errback(err):print 'errback got:', errd = defer.Deferred(canceller) # created by lower-level code
d.addCallbacks(callback, errback) # added by higher-level code
d.cancel()
print 'done'

这个例子基本上跟第二个例子相同,除了有第三个回调(canceller).这个回调是我们在创建 Deferred 的时候传递给它的,不是之后添加的.这个回调负责执行终止异步操作时所需的上下文相关的具体操作(当然,仅当 deferred 被实际取消). canceller 回调是返回 deferred 的底层代码的必要部分,不是接收 deferred 的高层代码为其自己添加的回调和错误回调.

运行这个例子将产生如下输出:

I need to cancel this deferred: <Deferred at 0xb7669d2cL>
errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]
done

正如你所看到, 不需要返回结果的 deferred 被传递给 canceller 回调.在这里我们可以做任何需要做的事情以便彻底终止异步操作.注意 canceller 在错误回调链激发前被调用.其实我们可以在取消回调中选择使用任何结果或错误自己激发 deferred(这样就会优先于 CancelledError 失败).这两种情况在 deferred-cancel/defer-cancel-6.py 和 deferred-cancel/defer-cancel-7.py中进行了说明.

在激发 reactor 之前先做一个简单的测试.我们将使用 canceller 回调创建一个 deferred,正常的激发它,之后取消它.你可以在 deferred-cancel/defer-cancel-8.py 中看到代码.通过检查那个脚本的输出,你将看到取消一个被激发的 deferred 不会调用canceller 回调.这正是我们所要的,因为没什么可取消的.

我们目前看到的例子都没有实际的异步操作. 让我们构造一个调用异步操作的简单程序,之后我们将指出如何使那个操作可取消.

参见代码 deferred-cancel/defer-cancel-9.py:

from twisted.internet.defer import Deferreddef send_poem(d):print 'Sending poem'd.callback('Once upon a midnight dreary')def get_poem():"""Return a poem 5 seconds later."""from twisted.internet import reactord = Deferred()reactor.callLater(5, send_poem, d)return ddef got_poem(poem):print 'I got a poem:', poemdef poem_error(err):print 'get_poem failed:', errdef main():from twisted.internet import reactorreactor.callLater(10, reactor.stop) # stop the reactor in 10 secondsd = get_poem()d.addCallbacks(got_poem, poem_error)reactor.run()main()

这个例子中包含了一个 get_poem 函数,它使用 reactor 的 callLater 方法在被调用5秒钟后异步地返回一首诗.主函数调用 get_poem,添加一个回调/错误回调对,之后启动 reactor.我们(同样使用 callLater)安排 reactor 在10秒钟之后停止.通常我们向 deferred 添加一个回调来实现,但你很快就会知道我们为何这样做.

运行程序(适当延迟后)产生如下输出:

Sending poem
I got a poem: Once upon a midnight dreary

10秒钟后程序终止.现在来试试在诗歌被发送前取消 deferred.只需加入以下代码在2秒钟后取消(在5秒钟延迟发送诗歌之前):

reactor.callLater(2, d.cancel) # cancel after 2 seconds

完整的例子参见 deferred-cancel/defer-cancel-10.py,这将产生如下输出:

get_poem failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]
Sending poem

这个例子清晰地展示了取消一个 deferred 并没有取消它背后的异步请求.2秒钟后我们看到了错误回调输出,打印出如我们所料的 CancelledError 错误.但是5秒钟后我们看到了 send_poem 的输出(但是这个 deferred 上的回调并没有激发).

这时我们与 deferred-cancel/defer-cancel-4.py 的情况一样."取消" deferred 仅仅是使最终结果被忽略,但实际上并没有终止这个操作.正如我们上面所学,为了得到一个真正可取消的 deferred,必须在它被创建时添加一个 cancel 回调.

那么这个新的回调需要做什么呢? 参考一下关于 callLater 方法的 文档. 它的返回值是另一个实现了 IDelayedCall 的对象,用 cancel 方法我们可以阻止延迟的调用被执行.

这非常简单,更新后的代码参见 deferred-cancel/defer-cancel-11.py.所有相关变化都在 get_poem 函数中:

def get_poem():"""Return a poem 5 seconds later."""def canceler(d):# They don't want the poem anymore, so cancel the delayed calldelayed_call.cancel()# At this point we have three choices:#   1. Do nothing, and the deferred will fire the errback#      chain with CancelledError.#   2. Fire the errback chain with a different error.#   3. Fire the callback chain with an alternative result.d = Deferred(canceler)from twisted.internet import reactor
delayed_call = reactor.callLater(5, send_poem, d)return d

在这个新版本中,我们保存 callLater 的返回值以便能够在 cancel 回调中使用. cancel 回调的唯一工作是调用 delayed_call.cancel(). 但是正如之前讨论的,我们可以选择激发自定义的 deferred. 最新版本的程序产生如下输出:

get_poem failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]

正如你看到的, deferred 被取消了并且异步操作被真正地终止了(我们看不到 send_poem 的输出了).


诗歌代理 3.0

正如在简介中所讨论,诗歌代理服务器是实现取消的很好的候选者,因为这可以让我们取消诗歌下载如果事实证明没有人想要它(如客户端已经在我们发送诗歌前关闭了连接).版本 3.0的代理位于 twisted-server-4/poetry-proxy.py,实现了 deferred 取消. 变化首先位于 PoetryProxyProtocol:

class PoetryProxyProtocol(Protocol):def connectionMade(self):self.deferred = self.factory.service.get_poem()self.deferred.addCallback(self.transport.write)self.deferred.addBoth(lambda r: self.transport.loseConnection())def connectionLost(self, reason):if self.deferred is not None:deferred, self.deferred = self.deferred, Nonedeferred.cancel() # cancel the deferred if it hasn't fired

你可以与 旧版本 对比一下.两个主要的变化是:

  1. 保存我们从 get_poem 得到的 deferred,以便之后在需要时取消它.

  2. 当连接关闭时取消 deferred.注这个操作同样会取消 deferred 当我们实际得到诗歌之后,但正如前例所发现的,取消一个被激发的 deferred 不会有任何效果.

现在我们需要确保取消 deferred 将实际终止诗歌的下载. 所以我们需要改变 ProxyService:

class ProxyService(object):poem = None # the cached poemdef __init__(self, host, port):self.host = hostself.port = portdef get_poem(self):if self.poem is not None:print 'Using cached poem.'# return an already-fired deferredreturn succeed(self.poem)def canceler(d):print 'Canceling poem download.'factory.deferred = Noneconnector.disconnect()print 'Fetching poem from server.'deferred = Deferred(canceler)deferred.addCallback(self.set_poem)factory = PoetryClientFactory(deferred)from twisted.internet import reactorconnector = reactor.connectTCP(self.host, self.port, factory)return factory.deferreddef set_poem(self, poem):self.poem = poemreturn poem

同样,可以与 旧版本 对比一下. 这个类具有一些新的变化:

  1. 我们保存 reactor.connetTCP 的返回值,一个 IConnector 对象.我们可以使用这个对象上的 disconnect 方法关闭连接.

  2. 我们创建带 canceler 回调的 deferred.那个回调是一个闭包,它使用 connector 关闭连接. 但首先须设置 factory.deferred 属性为 None. 否则,工厂会以 "连接关闭"错误回调激发 deferred 而不是以 CancelledError 激发. 由于deferred 已经被取消了, 以 CancelledError 激发更加合适.

你同样会注意到我们是在 ProxyService 中创建 deferred 而不是 PoetryClientFactory. 由于 canceler 回调需要获取IConnector 对象, ProxyService 成为最方便创建 deferred 的地方.

同时,就像我们之前的例子, canceler 回调作为一个闭包实现.闭包看起来在取消回调的实现上非常有用.

让我们试试新的代理.首先启动一个慢服务器.它需要很慢以便我们有时间取消:

python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt

现在可以启动代理(记住你需要Twisted 10.1.0):

python twisted-server-4/poetry-proxy.py --port 10000 10001

现在我们可以用任何客户端从代理下载一首诗,或者仅使用 curl:

curl localhost:10000

几秒钟后,按 Ctrl-C 停止客户端或者 curl 进程. 在终端运行代理你将看到如下输出:

Fetching poem from server.
Canceling poem download.

你应该看到慢服务器已经停止了向输出打印它所发送诗歌的片段,因为我们的代理挂了.

你可以多次启动和停止客户端来证实每个下载每次都被取消了.但是如果你让整首诗运行完,那么代理将缓存它并且在此之后立即发送它.


另一个难点

以上我们曾不止一次说取消一个已经激发的 deferred 是没有效果的.然而,这不是十分正确.在 :doc:`p13` 中,我们学习了附加给一个 deferred 的回调和错误回调也可能返回另一个 deferred.在那种情况下,原始的(外层) deferred 暂停执行它的回调链并且等待内层 deferred 激发(参见 `figure28`_).

如此, 即使一个 deferred 激发了发出异步请求的高层代码,它也不能接收到结果,因为在等待内层 deferred 完成之前回调链暂停了. 所以当高层代码取消这个外部 deferred 时会发生什么情况呢? 在这种情况下,外部 deferred 不仅仅是取消它自己(它已经激发了);相反地,这个 deferred 取消内部的 deferred.

所以当你取消一个 deferred 时,你可能不是在取消主异步操作,而是一些其他的作为前者结果所触发的异步操作.呼!

我们可以用一个例子来说明.考虑代码 deferred-cancel/defer-cancel-12.py:

from twisted.internet import deferdef cancel_outer(d):print "outer cancel callback."def cancel_inner(d):print "inner cancel callback."def first_outer_callback(res):print 'first outer callback, returning inner deferred'return inner_ddef second_outer_callback(res):print 'second outer callback got:', resdef outer_errback(err):print 'outer errback got:', errouter_d = defer.Deferred(cancel_outer)
inner_d = defer.Deferred(cancel_inner)outer_d.addCallback(first_outer_callback)
outer_d.addCallbacks(second_outer_callback, outer_errback)outer_d.callback('result')# at this point the outer deferred has fired, but is paused
# on the inner deferred.print 'canceling outer deferred.'
outer_d.cancel()print 'done'

在这个例子中,我们创建了两个 deferred, outer 和 inner,并且有一个外部回调返回内部 deferred. 首先,我们激发外部deferred,然后取消它. 输出结果如下:

first outer callback, returning inner deferred
canceling outer deferred.
inner cancel callback.
outer errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:
]
done

正如你看到的,取消外部 deferred 并没有使外部 cancel 回调被激发. 相反,它取消了内部 deferred,所以内部 cancel 回调被激发了,之后外部错误回调收到 CancelledError (来自内部 deferred).

你可能需要仔细看一看那些代码,并且做些变化看看如何影响结果.


讨论

取消 deferred 是非常有用的操作,使我们的程序避免去做不需要的工作. 然而正如我们看到的,它可能有一点点棘手.

需要明白的一个重要事实是取消一个 deferred 并不意味着取消了它后面的异步操作.事实上,当写这篇文章时,很多 deferreds并不会被真的"取消",因为大部分Twisted代码写于Twisted 10.1.0之前并且还没有被升级.这包括很多Twisted本身的APIs!检查文档或源代码去发现"取消 deferred"是否真的取消了背后的请求,还是仅仅忽略它.

第二个重要事实是从你的异步APIs返回的 deferred 并不一定在完整意义上可取消. 如果你希望在自己的程序中实现取消,你应该先研究一下Twisted源代码中的许多例子. Cancellation 是一个暂新的特性,所以它的模式和最好实践还在制定当中.


展望未来

现在我们已经学习了关于 Deferreds 的方方面面以及Twisted背后的核心概念. 这意味着我们没什么需要介绍的了,因为Twisted的其余部分主要包括一些特定的应用,如网络编程或异步数据库处理.故而,在 接下来 的部分中,我们想走点弯路,看看其他两个使用异步I/O的系统跟Twisted有何理念相似之处.之后,在尾声中,我们会打个包并且建议一些帮助你继续学习Twisted的方法.


参考练习

  1. 你知道你可以用多种方式拼写"cancelled"吗? 真的. 这取决于你的心情.

  2. 细读 Deferred 类的源代码,关注 cancellation 的实现.

  3. 在Twisted 10.1.0的 源码 中找具有取消回调的 deferred 的例子.研究它们的实现.

  4. 修改我们诗歌客户端中 get_poetry 方法返回的 deferred, 使其可取消.

  5. 做一个基于 reactor 的例子展示取消外部 deferred,它被内层 deferred 暂停了.如果使用 callLater 你需要小心选择延迟时间,以确保外层 deferred 在正确的时刻被取消.

  6. 找一个 Twisted 中还不支持"本质上取消操作"的异步API,为它实现本质取消. 并向 Twisted项目 提交一个 补丁.不要忘记单元测试!


转载于:https://my.oschina.net/CandyMi/blog/610325

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543696.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mac 获取 Brew

2019独角兽企业重金招聘Python工程师标准>>> 终端输入 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 转载于:https://my.oschina.net/fdstudio/blog/610680

express 项目生成器_用于项目的Express模板生成器(2)| 应用程序结构研究

express 项目生成器Hello! In express template generator for your projects (1), we looked at express generator and how we can start an express application with stressing to build a brand new structure of all required files. 你好&#xff01; 在针对您的项目的E…

Linux 服务器中文乱码编码解决

Linux环境的ECS中&#xff0c;若出现如下中文显示为乱码的情况。 一般原因如下: 1. 未安装中文语言包 2. 未设置正确的默认语言 3. SSH 终端未正确配置 本文以Centos 6.5为例&#xff0c;演示如何解决中文乱码问题。 1. 使用 locale -a |grep zh_CN查看系统是否已经安装…

ldo regula_使用C中的Regula Falsi方法找到复多项式方程的根

ldo regulaRegula Falsi方法 (Regula Falsi method) About the method: 关于方法&#xff1a; We often hear many children and even many adults complaining about the difficulty level that they face while solving complex polynomial equations. It is also difficult…

go newscanner判断文件读取结束_Go单元测试-testing

在开发程序中&#xff0c;很重要一点就是测试&#xff0c;测试可以保证代码的质量&#xff0c;保证每个函数可以正常运行。但是如何保证写出来的程序是否正确。单元测试一般是用来测试我们的代码逻辑有没有问题&#xff0c;有没有按照我们期望的运行&#xff0c;以保证代码质量…

nextdate函数白盒测试问题 软件测试_软件测试基本常识

一、软件测试的分类&#xff1a;1.按照是否执行被测试软件来分&#xff1a;静态测试&#xff1a;是指不运行软件&#xff0c;测试包括代码检查、静态结构分析、代码质量度量等&#xff0c;主要对软件需求说明书、设计说明书、软件源代码进行检查与分析。 动态测试&#xff1a;…

ideatomcat老是运行以前的项目_日“吞”150吨垃圾,禅城集中式餐厨垃圾处理项目启用...

12月9日&#xff0c;禅城区集中式餐厨垃圾处理项目正式投料试运行&#xff0c;该项目如今每天可处理150吨垃圾。这意味着禅城区将通过先进技术实现餐厨垃圾资源化、减量化、无害化处理。禅城区集中式餐厨垃圾处理项目位于佛山市南庄污水处理厂首期工程北侧&#xff0c;禅港路西…

java怎么知道上传文件是否成功_文件包含漏洞之——tomcat CVE-2020-1938漏洞复现

这个漏洞是今年2月份出现的&#xff0c;他的影响范围也是非常广的。2月20日&#xff0c;国家信息安全漏洞共享平台&#xff08;CNVD&#xff09;发布了Apache Tomcat文件包含漏洞&#xff08;CNVD-2020-10487/CVE-2020-1938&#xff09;&#xff0c;这个漏洞是由于Tomcat AJP协…

css word-wrap_CSS中分词“ break-all”和“ break-word”的值之间的差异

css word-wrapDefinition: 定义&#xff1a; What is the most fundamental element that comes to mind when you are considering to develop a web page? Words! If that was your answer, then pat yourself because you are already aware of what we are going to disc…

Android Studio apk 打包流程

1.Build -> Generate Signed APK...&#xff0c;打开如下窗口 2.假设这里没有打过apk包&#xff0c;点击Create new&#xff0c;窗口如下 这里只要输入几个必要项 Key store path&#xff08;生产key文件的保存路径 &#xff09; Key store password&#xff08;key 存储密码…

【Android】11.3 屏幕旋转和场景变换过程中GridView的呈现

分类&#xff1a;C#、Android、VS2015&#xff1b; 创建日期&#xff1a;2016-02-21 一、简介 实际上&#xff0c;对于布局文件中的View来说&#xff0c;大多数情况下&#xff0c;Android都会自动保存这些状态&#xff0c;并不需要我们都去处理它。这一节仍以GridView为例&…

html---textarea初始化时就有个table空格以及tab键操作无效

1 初始化时就有一个tab空格这是由于<textarea></textarea>之间的内容不为空的原因&#xff0c;包含空格和换行&#xff0c;否则浏览器会觉得空格或者换行都是文本域的内容。因此书写时需将<textarea></textarea>紧靠在一起。2 tab键对textarea操作无效…

android decorView详解

摘要 一、DecorView为整个Window界面的最顶层View。 二、DecorView只有一个子元素为LinearLayout。代表整个Window界面&#xff0c;包含通知栏&#xff0c;标题栏&#xff0c;内容显示栏三块区域。 三、LinearLayout里有两个FrameLayout子元素。 (20)为标题栏显示界面。只有一个…

3dmax批量导入obj_ArcGIS 与 3DMax 结合建模

整体技术思路是将项目区二维的CAD测绘底图&#xff0c;通过整理导入到3DMax中&#xff0c;根据CAD底图为基础&#xff0c;绘制三维数字模型。利用Photoshop 平面图像处理软件&#xff0c;对现场采集的照片进行修整&#xff0c;为三维模型制作表面贴图。最终把贴好材质的三维楼体…

字符串乘一个数_【思维拓展】三位数乘两位数,构造最大积和最小积

前面袁老师给大家讲了一个重要结论&#xff0c;并运用这个结论来解决问题&#xff0c;构造两位数乘两位数最大积和最小积的问题。今天&#xff0c;更进一步&#xff0c;三位数乘两位数中&#xff0c;如何构造最大积和最小的积&#xff1f;【问题引入】用9、8、6、5、4这五个数字…

ios 微信支付

服务器签名版本 官方已经是建议使用服务器签名来接入微信支付&#xff0c;实际上从安全上考虑&#xff0c;确实是每个客户端不应该知道RAS密钥&#xff0c;也不需要每个客户端都写一遍签名的算法。 服务端接入流程文档&#xff1a;https://pay.weixin.qq.com/wiki/doc/api/app.…

macos可以升级到指定版本吗_承装承修承试可以跨级升级吗?

在建筑行业&#xff0c;通常我们所说的承装承修承试&#xff0c;也就是指承装(修、试)电力设施许可证。承装(修、试)电力设施许可证的功能作用相当于建筑资质&#xff0c;企业需要办理许可证后才能承接电力设施的安装、维护、调试等工程项目。承装(修、试)电力设施许可证可以办…

一个事物两个方面的对比举例_顶管施工也有讲究,两个方面一个个来

顶管施工其实就是我们平时说的不开挖或者非开挖施工啦&#xff0c;其原理是借助于主顶油缸及管道间、中继间等推力&#xff0c;把工具管或掘进机从工作坑内穿过土层一直推进到接收坑内吊起。管道紧随工具管或掘进机后&#xff0c;埋设在两坑之间。为了响应中央的号召&#xff1…

SQLServer中的死锁的介绍

简介 什么是死锁&#xff1f; 我认为&#xff0c;死锁是由于两个对象在拥有一份资源的情况下申请另一份资源&#xff0c;而另一份资源恰好又是这两对象正持有的&#xff0c;导致两对象无法完成操作&#xff0c;且所持资源无法释放。 什么又是阻塞&#xff1f; 阻塞是由于资源不…

解析取值_圆锥曲线——高中解析几何全归纳

这是一系列文章&#xff0c;我将在接下来了80多天&#xff0c;尽力把理科比较难的大题题型全部归纳一下然后在最后我会告诉做解析几何的窍门&#xff0c;让你的解析几何不再没有头绪&#xff0c;拿到既可做全文干货&#xff0c;不掺水&#xff0c;可以说总结了解析几何中你能遇…