{"id":310895,"date":"2023-01-05T16:02:08","date_gmt":"2023-01-05T15:02:08","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=dotnet&#038;p=310895"},"modified":"2025-09-18T08:54:46","modified_gmt":"2025-09-18T07:54:46","slug":"resharper-out-of-process","status":"publish","type":"dotnet","link":"https:\/\/blog.jetbrains.com\/zh-hans\/dotnet\/2023\/01\/05\/resharper-out-of-process","title":{"rendered":"The Road to Out-of-Process ReSharper: Asynchronous Typing"},"content":{"rendered":"\n<p>Taking ReSharper out of process is a monumental undertaking. We covered the magnitude of this <a href=\"https:\/\/blog.jetbrains.com\/dotnet\/2020\/02\/24\/update-running-resharper-process\/\">in a previous blog post<\/a> and posted some <a href=\"https:\/\/youtrack.jetbrains.com\/issue\/RSRP-480863\" target=\"_blank\" rel=\"noopener\">updates on YouTrack<\/a> along the way.&nbsp;&nbsp;<\/p>\n\n\n\n<p>In the simplest terms, we\u2019re tasked with separating code that requires the Microsoft Visual Studio (VS) API from code that works with the structures ReSharper uses to store information about your solution (akin to Roslyn\u2019s workspace).&nbsp;<\/p>\n\n\n\n<p>The overarching goal is to leave the part of code that requires the VS API within the Visual Studio process, and to take the ReSharper-bound code out into a separate process, with the two sides communicating through <a href=\"https:\/\/github.com\/JetBrains\/rd\/\" target=\"_blank\" rel=\"noopener\">the open-source Rd-protocol<\/a> that also powers Rider and several of our remote development tools. Many components have already been made compatible with this protocol, and the code that belongs to the editor is next in line.&nbsp;<\/p>\n\n\n\n<p>In this article we\u2019re going to go in-depth on a single task within the larger scope of work we\u2019ve been tackling lately. A task that \u2013 if executed successfully \u2013 will reduce typing lag for ReSharper, both in its current integrated state and once it has gone out of process.&nbsp;<\/p>\n\n\n                    <div class=\"alert \">\n            <p><strong>Note:<\/strong> Asynchronous typing has been enabled for ReSharper 2024.2 and newer. u003cbru003eu003ca href=u0022https:\/\/www.jetbrains.com\/resharper\/download\/u0022u003eGive it a try!u003c\/au003e<\/p>\n        <\/div>\n    \n\n\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The complicated backstory<\/strong><\/h2>\n\n\n\n<p>ReSharper uses a mutable model of your solution, synced to the version the user sees on their screen. This type of model is protected by a shared <a href=\"https:\/\/en.wikipedia.org\/wiki\/Readers%E2%80%93writer_lock\" target=\"_blank\" rel=\"noopener\">readers-writer lock<\/a>. This means that whenever any sort of action is performed within the editor (even as small as typing a character), ReSharper has to stop all of its activities under the <em>read <\/em>lock, request permission to <em>write<\/em> in order<em> <\/em>to edit the model and \u2013 having received said permission \u2013 sync the models and resume all interrupted activities.&nbsp;<\/p>\n\n\n\n<p>On average, it takes less than 20 milliseconds to process a single action. However, some outlier cases can occur where interrupting the background data processing operation is complicated. We constantly monitor the system for such operations to ensure that interruptions happen as quickly as possible.&nbsp;<\/p>\n\n\n\n<p>To understand how difficult it is to untangle and separate the different operations involved in even the simplest editing actions, it\u2019s necessary to clearly understand the role ReSharper plays in editing. ReSharper embeds itself into the chain of user input handlers and each keystroke triggers the following sequence of actions:&nbsp;<\/p>\n\n\n\n<ol>\n<li>Requests a blocking <em>write<\/em>-lock to update the data structures.<\/li>\n\n\n\n<li>Receives write-lock and pauses all async operations, like file analysis, as the results of these operations can no longer be accurate.<\/li>\n\n\n\n<li>Provides typing assistance (code formatting, smart code completion, etc.) along with the user typing.&nbsp;<\/li>\n\n\n\n<li>Requests a calculation for the updated completion items.<\/li>\n\n\n\n<li>Releases the blocking write-lock.<\/li>\n<\/ol>\n\n\n\n<p>For predictable model changes, ReSharper performs the synchronous refresh reasonably quickly and immediately launches asynchronous code completion calculations and error analysis, as well as restarts the interrupted activities if needed. Things get trickier when typing assistance is involved.<\/p>\n\n\n\n<p>Whenever a user inputs a symbol, ReSharper evaluates the symbol to see if there\u2019s any assistance it has to offer based on that symbol. If there is, a complex interaction between ReSharper and Visual Studio must occur. ReSharper synchronously comes up with the action it needs to perform after or instead of the expected symbol and passes that call down the chain of handlers, which leads to the appearance of an appropriate symbol in the editor, and only then does ReSharper complete the evaluation. The algorithm looks something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"702\" height=\"471\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/01\/writelock-on-typing-1.svg\" alt=\"\" class=\"wp-image-310977\"\/><\/figure>\n\n\n\n<p>However, the code executed by ReSharper to complete an action in the editor can be quite complex. For example, if the file that\u2019s being edited is large and the user adds a curly bracket, ReSharper\u2019s search for the spot where the second bracket should go may require reparsing most of the file, which can be time-consuming. And if it\u2019s the closing bracket that\u2019s being put in \u2013 that kicks the difficulty up another notch.&nbsp;<\/p>\n\n\n\n<p>The interaction between ReSharper and Visual Studio takes extra time and causes lag in typing and code completion. That\u2019s why it\u2019s important to come up with an asynchronous way to handle the changes made to the file in the editor.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Solutions and obstacles<\/strong><\/h2>\n\n\n\n<p>We believe that the answer to the problem is to make the file editing in Visual Studio and code model editing in ReSharper asynchronous.&nbsp;<\/p>\n\n\n\n<p>Doing so will free us from the need to request a<em> <\/em>blocking<em> write<\/em>-lock the moment the user starts typing in the editor. In most scenarios, this will decrease typing lag. It will also eliminate the need to wait for the background activities in the same thread to break. We won\u2019t get in the way of the editor displaying the input symbol immediately. And once the <em>write<\/em>-lock is lifted, writing will be immediately available as there won\u2019t be any operations requiring a<em> read<\/em>-lock.&nbsp;<\/p>\n\n\n\n<p>This is where we run into our first obstacle. We need a clear understanding of how the changes made to the document on the Visual Studio side match up against the changes we make on our side. For example, what does the editor do when the user enters two closing brackets? We can\u2019t evaluate the result of those two actions on Visual Studio\u2019s part, because we don\u2019t have a parser and a formatter in context here to complete this operation.&nbsp;<\/p>\n\n\n\n<p>That\u2019s why we need to pass this input data on to the second process, which can apply the changes to a virtual document and then play it back in the user\u2019s interface once the changes have been calculated. But the trouble comes when a second set of formatting inputs comes in before the calculations for the first one can be completed, rendering the work obsolete as the second input is dependent on the outcome of the first.&nbsp;<\/p>\n\n\n\n<p>To solve this problem, we\u2019re implementing a change log and logging our document changes according to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Operational_transformation\" target=\"_blank\" rel=\"noopener\">Operational Transformations<\/a> consistency models. In doing so, a scenario where two brackets are entered in a close sequence can be resolved by having the operation of adding a second symbol be logged, reversed, and then re-applied to the changed model once the result of the first operation is completed.&nbsp;<\/p>\n\n\n\n<p>The resulting flow looks admittedly overwhelming\u2026&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"702\" height=\"632\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/01\/writelock-on-typing-after-Page-1.drawio.svg\" alt=\"\" class=\"wp-image-311035\"\/><\/figure>\n\n\n\n<p>\u2026but it essentially boils down to this: ReSharper\u2019s typing processing becomes asynchronous to Visual Studio\u2019s backend, saving you some precious milliseconds on the back-and-forth that can produce lag and graphical artifacts.&nbsp;<\/p>\n\n\n\n<p>Are you with us so far? Because we\u2019re about to go even deeper down this OOP rabbit hole.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image is-resized\"><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/MjThmSfucXFdA2pklXerl4wz32JFquN7FKFiilzhcq2qODGPLyueiF1PsM8KaySAyuMjXN9s8rie8jGOE3Q1JGc1R-VI1e0jD-RojAbSAObjU51DgnJg29NapflOjJ5Rn_oqc6oq82fU1dlw3mHeBiOlDDiK1J3_bAOwspZcVlBpjOVxq4Gd5btimYjlng.png\" alt=\"\" style=\"width:693px;height:537px\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The challenges ahead<\/strong><\/h2>\n\n\n\n<p>We\u2019re excited to share that we <em>do<\/em> have an internal prototype at this time. However, the prototype is not of stable release quality, and here\u2019s why:<\/p>\n\n\n\n<p>1. &nbsp; Some of ReSharper\u2019s features support the \u201cdouble undo\u201d principle. For example, the user can use Ctrl+Z to undo the formatting and then use it again to remove the symbol it was applied to. This complicates the operation, and there is no way to be sure if the change will be applied or rejected.&nbsp;<\/p>\n\n\n\n<p>2. &nbsp; To reduce the amount of falsely shown intermediate frames, which are almost unavoidable while waiting for feedback from another process, we\u2019re trying to predict how the changes will unfold. Such predictions are impossible to make with code formatting or complex code assistance. However, it\u2019s achievable for simpler cases. Currently, we don\u2019t employ any oracles, but they are factored into the architecture.<\/p>\n\n\n\n<p>3. &nbsp; Implementing asynchronous typing brings a major paradigm shift for ReSharper. This means we\u2019re moving away from the paradigm that we\u2019ve been building and enforcing for years. To make sure we don\u2019t accidentally break existing features and development flows, we have put this functionality under a feature flag so we can easily test-drive asynchronous typing without bothering you until we\u2019re confident that we\u2019ve covered all cases. This does mean that we have to support both synchronous and asynchronous typing during the migration period, at least until we\u2019re ready to make this feature flag the default.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>ReSharper has a large and loyal user base who rely on our product to perform at their highest level, and we can\u2019t bring ourselves to offer anything less than a stable build that we can be proud of. We hope that this post paints a clearer picture of the many obstacles we have yet to overcome on our journey out of Visual Studio\u2019s process. We thank you for your patience and welcome any questions you may have.&nbsp;<\/p>\n\n\n\n<p><em>Special thanks to Alexander Ulitin for spearheading this project, as well as to Sergey Kuks and Alexander Kurakin for their help in putting this article together.&nbsp;<\/em><\/p>\n","protected":false},"author":1337,"featured_media":310955,"comment_status":"closed","ping_status":"closed","template":"","categories":[4992,3990],"tags":[2863,46,8717],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/dotnet\/310895"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/dotnet"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/types\/dotnet"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/users\/1337"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/comments?post=310895"}],"version-history":[{"count":10,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/dotnet\/310895\/revisions"}],"predecessor-version":[{"id":628258,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/dotnet\/310895\/revisions\/628258"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media\/310955"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=310895"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/categories?post=310895"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/tags?post=310895"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/cross-post-tag?post=310895"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}