Skip to content

Commit 9084ce2

Browse files
committed
Add JumpToReferenceAsync() overload to allow detecting when the decompilation after the jump has finished.
1 parent 0894e4c commit 9084ce2

9 files changed

Lines changed: 312 additions & 65 deletions

File tree

ILSpy.BamlDecompiler/BamlResourceEntryNode.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,22 @@ public BamlResourceEntryNode(string key, Stream data) : base(key, data)
2626

2727
public override bool View(DecompilerTextView textView)
2828
{
29-
AvalonEditTextOutput output = new AvalonEditTextOutput();
3029
IHighlightingDefinition highlighting = null;
3130

3231
textView.RunWithCancellation(
3332
token => Task.Factory.StartNew(
3433
() => {
34+
AvalonEditTextOutput output = new AvalonEditTextOutput();
3535
try {
3636
if (LoadBaml(output))
3737
highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".xml");
3838
} catch (Exception ex) {
3939
output.Write(ex.ToString());
4040
}
4141
return output;
42-
}, token),
43-
t => textView.ShowNode(t.Result, this, highlighting)
44-
);
42+
}, token))
43+
.Then(output => textView.ShowNode(output, this, highlighting))
44+
.HandleExceptions();
4545
return true;
4646
}
4747

ILSpy/App.xaml.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using System.IO;
2424
using System.Linq;
2525
using System.Reflection;
26+
using System.Threading.Tasks;
2627
using System.Windows;
2728
using System.Windows.Documents;
2829
using System.Windows.Navigation;
@@ -92,6 +93,7 @@ public App()
9293
AppDomain.CurrentDomain.UnhandledException += ShowErrorBox;
9394
Dispatcher.CurrentDispatcher.UnhandledException += Dispatcher_UnhandledException;
9495
}
96+
TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException;
9597

9698
EventManager.RegisterClassHandler(typeof(Window),
9799
Hyperlink.RequestNavigateEvent,
@@ -111,6 +113,12 @@ string FullyQualifyPath(string argument)
111113
return argument;
112114
}
113115
}
116+
117+
void DotNet40_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
118+
{
119+
// On .NET 4.0, an unobserved exception in a task terminates the process unless we mark it as observed
120+
e.SetObserved();
121+
}
114122

115123
#region Exception Handling
116124
static void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)

ILSpy/Commands/DecompileAllCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public override void Execute(object parameter)
6161
}
6262
});
6363
return output;
64-
}, ct), task => MainWindow.Instance.TextView.ShowText(task.Result));
64+
}, ct)).Then(output => MainWindow.Instance.TextView.ShowText(output)).HandleExceptions();
6565
}
6666
}
6767
}

ILSpy/ILSpy.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
<SubType>Code</SubType>
178178
</Compile>
179179
<Compile Include="Commands\SimpleCommand.cs" />
180+
<Compile Include="TaskHelper.cs" />
180181
<Compile Include="TextView\FoldingCommands.cs" />
181182
<Compile Include="TreeNodes\Analyzer\AnalyzeContextMenuEntry.cs" />
182183
<Compile Include="TreeNodes\Analyzer\AnalyzedAssemblyTreeNode.cs" />

ILSpy/MainWindow.xaml.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,19 @@ public ILSpyTreeNode FindTreeNode(object reference)
558558

559559
public void JumpToReference(object reference)
560560
{
561+
JumpToReferenceAsync(reference).HandleExceptions();
562+
}
563+
564+
/// <summary>
565+
/// Jumps to the specified reference.
566+
/// </summary>
567+
/// <returns>
568+
/// Returns a task that will signal completion when the decompilation of the jump target has finished.
569+
/// The task will be marked as canceled if the decompilation is canceled.
570+
/// </returns>
571+
public Task JumpToReferenceAsync(object reference)
572+
{
573+
decompilationTask = TaskHelper.CompletedTask;
561574
ILSpyTreeNode treeNode = FindTreeNode(reference);
562575
if (treeNode != null) {
563576
SelectNode(treeNode);
@@ -569,6 +582,7 @@ public void JumpToReference(object reference)
569582

570583
}
571584
}
585+
return decompilationTask;
572586
}
573587
#endregion
574588

@@ -627,6 +641,7 @@ void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e)
627641
DecompileSelectedNodes();
628642
}
629643

644+
Task decompilationTask;
630645
bool ignoreDecompilationRequests;
631646

632647
void DecompileSelectedNodes(DecompilerTextViewState state = null, bool recordHistory = true)
@@ -646,7 +661,7 @@ void DecompileSelectedNodes(DecompilerTextViewState state = null, bool recordHis
646661
if (node != null && node.View(decompilerTextView))
647662
return;
648663
}
649-
decompilerTextView.Decompile(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state });
664+
decompilationTask = decompilerTextView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state });
650665
}
651666

652667
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)

ILSpy/TaskHelper.cs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System;
20+
using System.Threading;
21+
using System.Threading.Tasks;
22+
using ICSharpCode.ILSpy.TextView;
23+
24+
namespace ICSharpCode.ILSpy
25+
{
26+
public static class TaskHelper
27+
{
28+
public static readonly Task CompletedTask = FromResult<object>(null);
29+
30+
public static Task<T> FromResult<T>(T result)
31+
{
32+
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
33+
tcs.SetResult(result);
34+
return tcs.Task;
35+
}
36+
37+
public static Task<T> FromException<T>(Exception ex)
38+
{
39+
var tcs = new TaskCompletionSource<T>();
40+
tcs.SetException(ex);
41+
return tcs.Task;
42+
}
43+
44+
public static Task<T> FromCancellation<T>()
45+
{
46+
var tcs = new TaskCompletionSource<T>();
47+
tcs.SetCanceled();
48+
return tcs.Task;
49+
}
50+
51+
/// <summary>
52+
/// Sets the result of the TaskCompletionSource based on the result of the finished task.
53+
/// </summary>
54+
public static void SetFromTask<T>(this TaskCompletionSource<T> tcs, Task<T> task)
55+
{
56+
switch (task.Status) {
57+
case TaskStatus.RanToCompletion:
58+
tcs.SetResult(task.Result);
59+
break;
60+
case TaskStatus.Canceled:
61+
tcs.SetCanceled();
62+
break;
63+
case TaskStatus.Faulted:
64+
tcs.SetException(task.Exception.InnerExceptions);
65+
break;
66+
default:
67+
throw new InvalidOperationException("The input task must have already finished");
68+
}
69+
}
70+
71+
/// <summary>
72+
/// Sets the result of the TaskCompletionSource based on the result of the finished task.
73+
/// </summary>
74+
public static void SetFromTask(this TaskCompletionSource<object> tcs, Task task)
75+
{
76+
switch (task.Status) {
77+
case TaskStatus.RanToCompletion:
78+
tcs.SetResult(null);
79+
break;
80+
case TaskStatus.Canceled:
81+
tcs.SetCanceled();
82+
break;
83+
case TaskStatus.Faulted:
84+
tcs.SetException(task.Exception.InnerExceptions);
85+
break;
86+
default:
87+
throw new InvalidOperationException("The input task must have already finished");
88+
}
89+
}
90+
91+
public static Task Then<T>(this Task<T> task, Action<T> action)
92+
{
93+
if (action == null)
94+
throw new ArgumentNullException("action");
95+
return task.ContinueWith(t => action(t.Result), CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
96+
}
97+
98+
public static Task<U> Then<T, U>(this Task<T> task, Func<T, U> func)
99+
{
100+
if (func == null)
101+
throw new ArgumentNullException("func");
102+
return task.ContinueWith(t => func(t.Result), CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
103+
}
104+
105+
public static Task Then<T>(this Task<T> task, Func<T, Task> asyncFunc)
106+
{
107+
if (asyncFunc == null)
108+
throw new ArgumentNullException("asyncFunc");
109+
return task.ContinueWith(t => asyncFunc(t.Result), CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
110+
}
111+
112+
public static Task<U> Then<T, U>(this Task<T> task, Func<T, Task<U>> asyncFunc)
113+
{
114+
if (asyncFunc == null)
115+
throw new ArgumentNullException("asyncFunc");
116+
return task.ContinueWith(t => asyncFunc(t.Result), CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
117+
}
118+
119+
public static Task Then(this Task task, Action action)
120+
{
121+
if (action == null)
122+
throw new ArgumentNullException("action");
123+
return task.ContinueWith(t => {
124+
t.Wait();
125+
action();
126+
}, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
127+
}
128+
129+
public static Task<U> Then<U>(this Task task, Func<U> func)
130+
{
131+
if (func == null)
132+
throw new ArgumentNullException("func");
133+
return task.ContinueWith(t => {
134+
t.Wait();
135+
return func();
136+
}, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
137+
}
138+
139+
public static Task Then(this Task task, Func<Task> asyncAction)
140+
{
141+
if (asyncAction == null)
142+
throw new ArgumentNullException("asyncAction");
143+
return task.ContinueWith(t => {
144+
t.Wait();
145+
return asyncAction();
146+
}, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
147+
}
148+
149+
public static Task<U> Then<U>(this Task task, Func<Task<U>> asyncFunc)
150+
{
151+
if (asyncFunc == null)
152+
throw new ArgumentNullException("asyncFunc");
153+
return task.ContinueWith(t => {
154+
t.Wait();
155+
return asyncFunc();
156+
}, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
157+
}
158+
159+
/// <summary>
160+
/// If the input task fails, calls the action to handle the error.
161+
/// </summary>
162+
/// <returns>
163+
/// Returns a task that finishes successfully when error handling has completed.
164+
/// If the input task ran successfully, the returned task completes successfully.
165+
/// If the input task was cancelled, the returned task is cancelled as well.
166+
/// </returns>
167+
public static Task Catch<TException>(this Task task, Action<TException> action) where TException : Exception
168+
{
169+
if (action == null)
170+
throw new ArgumentNullException("action");
171+
return task.ContinueWith(t => {
172+
if (t.IsFaulted) {
173+
Exception ex = t.Exception;
174+
while (ex is AggregateException)
175+
ex = ex.InnerException;
176+
if (ex is TException)
177+
action((TException)ex);
178+
else
179+
throw t.Exception;
180+
}
181+
}, CancellationToken.None, TaskContinuationOptions.NotOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
182+
}
183+
184+
/// <summary>
185+
/// Ignore exceptions thrown by the task.
186+
/// </summary>
187+
public static void IgnoreExceptions(this Task task)
188+
{
189+
}
190+
191+
/// <summary>
192+
/// Handle exceptions by displaying the error message in the text view.
193+
/// </summary>
194+
public static void HandleExceptions(this Task task)
195+
{
196+
task.Catch<Exception>(exception => MainWindow.Instance.Dispatcher.BeginInvoke(new Action(delegate {
197+
AvalonEditTextOutput output = new AvalonEditTextOutput();
198+
output.Write(exception.ToString());
199+
MainWindow.Instance.TextView.ShowText(output);
200+
}))).IgnoreExceptions();
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)