Makes a method that blocks on tasks async: it awaits them instead and returns a task. Its callers get the task, and block on it where they used to get the value, so only the method itself changes shape. Convert to Async repeats this on each caller up to a boundary.
Target
The method, by symbol. There are no arguments.
Precondition
The method is an ordinary method with a body, block or expression, that does not already return a task, is not an iterator, does not return by reference and has no ref, out or in parameters. It is not virtual, abstract, an override or an interface implementation, whose signature it shares with other methods. It blocks on at least one task outside any lambda, local function or lock: task.Result, task.Wait() or task.GetAwaiter().GetResult(). It is only called, never used as a method group, where a method returning a task would no longer fit the delegate. The result compiles.
The method is marked async and returns Task<T> in place of T, or Task in place of void. A using System.Threading.Tasks; directive is added where the file needs one. Each blocking wait in the method becomes await on the task, parenthesised only where precedence needs it. A call of the method in the method itself or in another async method is awaited, unless it is in a lambda, local function, query or lock. Every other call blocks on the task with .GetAwaiter().GetResult(), so the caller keeps its signature and still gets the value.
Preserved
The values computed and the order calls are made in: a caller that blocks gets the value the method used to return, once the method has finished. Comments and layout around the changed calls and declaration. Waits inside lambdas and local functions, which may run later, keep blocking.
Limitations
Awaiting rethrows a task's exception as it is, where .Result and .Wait() wrapped it in an AggregateException; a caller catching AggregateException sees a different exception. Awaiting resumes on the captured synchronization context. A caller that blocks on that context's thread, as the callers left blocking do, can deadlock where the original wait did not, if the awaited task completed without needing the context. Method names keep no Async suffix; Rename adds one.
Error codes
Code Meaning nothing-to-awaitthe method does not block on a task already-asyncthe method already returns a task not-a-methodthe target is not an ordinary method polymorphic-methodthe method is virtual, an override or an interface implementation ref-parametersthe method has ref, out or in parameters iteratorthe method is an iterator method-group-referencethe method is used as a method group
Cases
Expand all · Collapse all
Diffs show each file from before to after .
/*[*/ … /*]*/ marks a selection and /*^*/ a caret; the runner removes them before the refactoring runs.
# across-projectsA caller in a project that references the method's project blocks on the task, and gets no using directive it does not need
success
Target symbol M:Stock.Inventory.Available(System.String)
Projects Core; App → Core
App/Report.cs modified
1 1 namespace Stock 2 2 { 3 3 public class Report 4 4 { 5 5 private readonly int _widgets; 6 6 7 7 public Report (Inventory inventory) 8 8 { 9 − _widgets = inventory.Available ("widget" ); 9 + _widgets = inventory.Available ("widget" ).GetAwaiter ().GetResult (); 10 10 } 11 11 12 12 public string Summary (Inventory inventory) 13 13 { 14 − return "Gadgets: " + inventory.Available ("gadget" ) + (inventory.InStock ("widget" ) ? "" : " (no widgets)" ); 14 + return "Gadgets: " + inventory.Available ("gadget" ).GetAwaiter ().GetResult () + (inventory.InStock ("widget" ) ? "" : " (no widgets)" ); 15 15 } 16 16 } 17 17 }
Core/Inventory.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Stock 4 4 { 5 5 public class Store 6 6 { 7 7 public Task <int > CountAsync (string sku) 8 8 { 9 9 return Task .FromResult (sku.Length ); 10 10 } 11 11 } 12 12 13 13 public class Inventory 14 14 { 15 15 private readonly Store _store = new Store (); 16 16 17 − public int Available (string sku) 17 + public async Task <int > Available (string sku) 18 18 { 19 − var count = _store.CountAsync (sku).Result ; 19 + var count = await _store.CountAsync (sku); 20 20 return count > 0 ? count - 1 : 0 ; 21 21 } 22 22 23 23 public bool InStock (string sku) 24 24 { 25 − return Available (sku) > 0 ; 25 + return Available (sku).GetAwaiter ().GetResult () > 0 ; 26 26 } 27 27 } 28 28 }
View fixture on GitHub
# already-async-rejectedRefuses a method that already returns a task
refusal
Target symbol M:Stock.Inventory.Available(System.String)
Refuses already-async; every file is left unchanged
Inventory.cs input
1 using System .Threading .Tasks ;2 3 namespace Stock 4 { 5 public class Inventory 6 { 7 public Task <int > Available (string sku) 8 { 9 return Task .FromResult (Task .FromResult (sku.Length ).Result ); 10 } 11 } 12 }
View fixture on GitHub
# async-caller-awaitsA wait written as GetAwaiter().GetResult() is awaited, and a caller that is already async awaits the call instead of blocking
success
Target symbol M:Data.Loader.Load
Loader.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Data 4 4 { 5 5 public class Loader 6 6 { 7 7 private readonly Task <string > _pending = Task .FromResult ("data" ); 8 8 9 − public string Load () 9 + public async Task <string > Load () 10 10 { 11 − return _pending.GetAwaiter ().GetResult (); 11 + return await _pending; 12 12 } 13 13 14 14 public async Task <int > LengthAsync () 15 15 { 16 16 await Task .Yield (); 17 − return Load ().Length ; 17 + return (await Load ()).Length ; 18 18 } 19 19 } 20 20 }
View fixture on GitHub
# callers-blockA method reading a task's Result awaits it and returns Task<int>; every caller, in its own file or another, blocks on the task it now returns
success
Target symbol M:Stock.Inventory.Available(System.String)
Inventory.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Stock 4 4 { 5 5 public class Store 6 6 { 7 7 public Task <int > CountAsync (string sku) 8 8 { 9 9 return Task .FromResult (sku.Length ); 10 10 } 11 11 } 12 12 13 13 public class Inventory 14 14 { 15 15 private readonly Store _store = new Store (); 16 16 17 − public int Available (string sku) 17 + public async Task <int > Available (string sku) 18 18 { 19 − var count = _store.CountAsync (sku).Result ; 19 + var count = await _store.CountAsync (sku); 20 20 return count > 0 ? count - 1 : 0 ; 21 21 } 22 22 23 23 public bool InStock (string sku) 24 24 { 25 − return Available (sku) > 0 ; 25 + return Available (sku).GetAwaiter ().GetResult () > 0 ; 26 26 } 27 27 } 28 28 }
Report.cs modified
1 1 namespace Stock 2 2 { 3 3 public class Report 4 4 { 5 5 private readonly int _widgets; 6 6 7 7 public Report (Inventory inventory) 8 8 { 9 − _widgets = inventory.Available ("widget" ); 9 + _widgets = inventory.Available ("widget" ).GetAwaiter ().GetResult (); 10 10 } 11 11 12 12 public string Summary (Inventory inventory) 13 13 { 14 − return "Gadgets: " + inventory.Available ("gadget" ) + (inventory.InStock ("widget" ) ? "" : " (no widgets)" ); 14 + return "Gadgets: " + inventory.Available ("gadget" ).GetAwaiter ().GetResult () + (inventory.InStock ("widget" ) ? "" : " (no widgets)" ); 15 15 } 16 16 } 17 17 }
View fixture on GitHub
# constructor-rejectedRefuses a constructor, which cannot be async
refusal
Target symbol M:Stock.Inventory.#ctor
Refuses not-a-method; every file is left unchanged
Inventory.cs input
1 using System .Threading .Tasks ;2 3 namespace Stock 4 { 5 public class Inventory 6 { 7 private readonly int _count; 8 9 public Inventory () 10 { 11 _count = Task .FromResult (3 ).Result ; 12 } 13 } 14 }
View fixture on GitHub
# generic-expression-bodiedAn expression-bodied generic method awaits in its expression body and returns Task<T>; a caller passing a type argument blocks
success
Target symbol M:Data.Settings.Load``1(System.String)
Settings.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Data 4 4 { 5 5 public class Cache 6 6 { 7 7 public Task <T > GetAsync <T >(string key) => Task .FromResult (default (T )); 8 8 } 9 9 10 10 public class Settings 11 11 { 12 12 private readonly Cache _cache = new Cache (); 13 13 14 − public T Load <T >(string key) => _cache.GetAsync <T >(key).Result ; 14 + public async Task <T > Load <T >(string key) => await _cache.GetAsync <T >(key); 15 15 16 − public int Timeout () => Load <int >("timeout" ) * 1000 ; 16 + public int Timeout () => Load <int >("timeout" ).GetAwaiter ().GetResult () * 1000 ; 17 17 } 18 18 }
View fixture on GitHub
# iterator-rejectedRefuses an iterator, which returns a sequence rather than running when called
refusal
Target symbol M:Stock.Inventory.Available(System.String)
Refuses iterator; every file is left unchanged
Inventory.cs input
1 using System .Collections .Generic ;2 using System .Threading .Tasks ;3 4 namespace Stock 5 { 6 public class Inventory 7 { 8 public IEnumerable <int > Available (string sku) 9 { 10 yield return Task .FromResult (sku.Length ).Result ; 11 } 12 } 13 }
View fixture on GitHub
# method-group-rejectedRefuses a method used as a method group, where a method returning a task would no longer fit the delegate
refusal
Target symbol M:Stock.Inventory.Available(System.String)
Refuses method-group-reference; every file is left unchanged
Inventory.cs input
1 using System ;2 using System .Threading .Tasks ;3 4 namespace Stock 5 { 6 public class Inventory 7 { 8 public int Available (string sku) 9 { 10 return Task .FromResult (sku.Length ).Result ; 11 } 12 13 public Func <string , int > Counter () 14 { 15 return Available ; 16 } 17 } 18 }
View fixture on GitHub
# nothing-to-await-rejectedRefuses a method that blocks on no task, which would become an async method with nothing to await
refusal
Target symbol M:Stock.Inventory.Available(System.String)
Refuses nothing-to-await; every file is left unchanged
Inventory.cs input
1 using System .Threading .Tasks ;2 3 namespace Stock 4 { 5 public class Inventory 6 { 7 public int Available (string sku) 8 { 9 return sku.Length ; 10 } 11 } 12 }
View fixture on GitHub
# nullable-referenceWith nullable reference types enabled, a method returning string? returns Task<string?>
success
Target symbol M:Data.Directory.Find(System.String)
Project nullable enable
Directory.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Data 4 4 { 5 5 public class Directory 6 6 { 7 7 public Task <string ?> FindAsync (string key) => Task .FromResult <string ?>(null ); 8 8 9 − public string ? Find (string key) 9 + public async Task <string ?> Find (string key) 10 10 { 11 − return FindAsync (key).Result ; 11 + return await FindAsync (key); 12 12 } 13 13 14 14 public string Describe (string key) 15 15 { 16 − return Find (key) ?? "none" ; 16 + return Find (key).GetAwaiter ().GetResult () ?? "none" ; 17 17 } 18 18 } 19 19 }
View fixture on GitHub
# ref-parameters-rejectedRefuses a method with an out parameter, which an async method cannot have
refusal
Target symbol M:Stock.Inventory.Available(System.String,System.Boolean@)
Refuses ref-parameters; every file is left unchanged
Inventory.cs input
1 using System .Threading .Tasks ;2 3 namespace Stock 4 { 5 public class Inventory 6 { 7 public int Available (string sku, out bool known) 8 { 9 known = true ; 10 return Task .FromResult (sku.Length ).Result ; 11 } 12 } 13 }
View fixture on GitHub
# virtual-method-rejectedRefuses a virtual method, whose signature overrides share
refusal
Target symbol M:Stock.Inventory.Available(System.String)
Refuses polymorphic-method; every file is left unchanged
Inventory.cs input
1 using System .Threading .Tasks ;2 3 namespace Stock 4 { 5 public class Inventory 6 { 7 public virtual int Available (string sku) 8 { 9 return Task .FromResult (sku.Length ).Result ; 10 } 11 } 12 }
View fixture on GitHub
# void-methodA void method that waits on a task returns Task, and a caller that is a statement blocks on it
success
Target symbol M:Data.Repository.Save(System.String)
Repository.cs modified
1 1 using System .Threading .Tasks ;2 2 3 3 namespace Data 4 4 { 5 5 public class Connection 6 6 { 7 7 public Task WriteAsync (string row) => Task .CompletedTask ; 8 8 } 9 9 10 10 public class Repository 11 11 { 12 12 private readonly Connection _connection = new Connection (); 13 13 14 − public void Save (string row) 14 + public async Task Save (string row) 15 15 { 16 − _connection.WriteAsync (row).Wait (); 16 + await _connection.WriteAsync (row); 17 17 } 18 18 19 19 public void SaveAll (string [] rows) 20 20 { 21 21 foreach (var row in rows) 22 − Save (row); 22 + Save (row).GetAwaiter ().GetResult (); 23 23 } 24 24 } 25 25 }
View fixture on GitHub